diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS index f892eb95dbc4..5eb465e0dd41 100644 --- a/.github/TEAM_MEMBERS +++ b/.github/TEAM_MEMBERS @@ -1,4 +1,5 @@ adamdotdevin +arvsrn Brendonovich fwang Hona @@ -7,11 +8,14 @@ jayair jlongster kitlangton kommander +ludvigrask MrMushrooooom nexxeln R44VC0RP rekram1-node thdxr simonklee +Slickstef11 +usrnk1 vimtor starptech diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 5b44517ec511..9d29724d86a4 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -8,6 +8,13 @@ inputs: runs: using: "composite" steps: + # node-gyp@latest (invoked via bunx for native install scripts) requires Node >=22; + # some runner images ship an older system Node on PATH + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "24" + - name: Get baseline download URL id: bun-url shell: bash diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml index 14e68701e57d..a83824e5cf60 100644 --- a/.github/workflows/compliance-close.yml +++ b/.github/workflows/compliance-close.yml @@ -34,10 +34,48 @@ jobs: const now = Date.now(); const twoHours = 2 * 60 * 60 * 1000; + const orgMemberAssociations = new Set(['OWNER', 'MEMBER']); + const agentLogin = 'opencode-agent[bot]'; + const { data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: '.github/TEAM_MEMBERS', + ref: 'dev', + }); + const teamMembers = new Set( + Buffer.from(file.content, 'base64') + .toString() + .split('\n') + .map((line) => line.trim().toLowerCase()) + .filter(Boolean) + ); + + function isExempt(item) { + const login = item.user?.login?.toLowerCase(); + return ( + login === agentLogin || + orgMemberAssociations.has(item.author_association) || + (login && teamMembers.has(login)) + ); + } for (const item of items) { const isPR = !!item.pull_request; const kind = isPR ? 'PR' : 'issue'; + const login = item.user?.login; + + if (isExempt(item)) { + core.info(`Skipping ${kind} #${item.number}; author ${login || 'unknown'} is exempt`); + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: item.number, + name: 'needs:compliance', + }); + } catch (e) {} + continue; + } const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 4648a2d0c3d3..3972247dafc6 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -17,12 +17,31 @@ jobs: with: fetch-depth: 1 + - name: Check exempt issue author + id: author + run: | + LOGIN="${{ github.event.issue.user.login }}" + ASSOCIATION="${{ github.event.issue.author_association }}" + + if [ "$LOGIN" = "opencode-agent[bot]" ] || + [ "$ASSOCIATION" = "OWNER" ] || + [ "$ASSOCIATION" = "MEMBER" ] || + grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - uses: ./.github/actions/setup-bun + if: steps.author.outputs.skip != 'true' - name: Install opencode + if: steps.author.outputs.skip != 'true' run: curl -fsSL https://opencode.ai/install | bash - name: Check duplicates and compliance + if: steps.author.outputs.skip != 'true' env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -38,6 +57,7 @@ jobs: opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created: Issue number: ${{ github.event.issue.number }} + Issue author association: ${{ github.event.issue.author_association }} Lookup this issue with gh issue view ${{ github.event.issue.number }}. @@ -49,6 +69,8 @@ jobs: Check whether the issue follows our contributing guidelines and issue templates. + If the issue author association is OWNER or MEMBER, skip this compliance check. Do not add the needs:compliance label for organization-owned issues. + This project has three issue templates that every issue MUST use one of: 1. Bug Report - requires a Description field with real content @@ -83,7 +105,7 @@ jobs: Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows: - If the issue is NOT compliant, start the comment with: + If the issue is NOT compliant and the author association is not OWNER or MEMBER, start the comment with: Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance @@ -129,12 +151,31 @@ jobs: with: fetch-depth: 1 + - name: Check exempt issue author + id: author + run: | + LOGIN="${{ github.event.issue.user.login }}" + ASSOCIATION="${{ github.event.issue.author_association }}" + + if [ "$LOGIN" = "opencode-agent[bot]" ] || + [ "$ASSOCIATION" = "OWNER" ] || + [ "$ASSOCIATION" = "MEMBER" ] || + grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - uses: ./.github/actions/setup-bun + if: steps.author.outputs.skip != 'true' - name: Install opencode + if: steps.author.outputs.skip != 'true' run: curl -fsSL https://opencode.ai/install | bash - name: Recheck compliance + if: steps.author.outputs.skip != 'true' env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -148,9 +189,12 @@ jobs: } run: | opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited. + Issue author association: ${{ github.event.issue.author_association }} Lookup this issue with gh issue view ${{ github.event.issue.number }}. + If the issue author association is OWNER or MEMBER, remove the needs:compliance label if present, delete the previous compliance comment if present, and do not post a new comment. + Re-check whether the issue now follows our contributing guidelines and issue templates. This project has three issue templates that every issue MUST use one of: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 037020c03af1..8981aad49a6d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,6 +6,7 @@ on: branches: - ci - dev + - v2 - beta - fix/npm-native-binary-install - snapshot-* @@ -31,6 +32,9 @@ permissions: contents: write packages: write +env: + OPENCODE_CHANNEL: ${{ (github.ref_name == 'v2' && 'next') || '' }} + jobs: version: runs-on: blacksmith-4vcpu-ubuntu-2404 @@ -122,7 +126,7 @@ jobs: - build-cli - version runs-on: blacksmith-4vcpu-windows-2025 - if: github.repository == 'anomalyco/opencode' + if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2' env: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} @@ -221,7 +225,7 @@ jobs: needs: - build-cli - version - if: github.repository == 'anomalyco/opencode' + if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2' continue-on-error: false env: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} @@ -447,6 +451,7 @@ jobs: path: packages/opencode/dist - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + if: github.ref_name != 'v2' with: name: opencode-cli-signed-windows path: packages/opencode/dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c69de1d93b0d..534a8d78a718 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - dev + - v2 pull_request: workflow_dispatch: @@ -74,13 +75,9 @@ jobs: working-directory: packages/client run: bun run check:generated - - name: Run HttpApi exerciser gates - if: runner.os == 'Linux' - working-directory: packages/opencode - run: bun run test:httpapi - e2e: name: e2e (${{ matrix.settings.name }}) + if: github.ref_name != 'v2' && github.head_ref != 'v2' strategy: fail-fast: false matrix: diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 27852a12ce4d..0350e43877c4 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -16,13 +16,32 @@ jobs: with: fetch-depth: 1 + - name: Check exempt issue author + id: author + run: | + LOGIN="${{ github.event.issue.user.login }}" + ASSOCIATION="${{ github.event.issue.author_association }}" + + if [ "$LOGIN" = "opencode-agent[bot]" ] || + [ "$ASSOCIATION" = "OWNER" ] || + [ "$ASSOCIATION" = "MEMBER" ] || + grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - name: Setup Bun + if: steps.author.outputs.skip != 'true' uses: ./.github/actions/setup-bun - name: Install opencode + if: steps.author.outputs.skip != 'true' run: curl -fsSL https://opencode.ai/install | bash - name: Triage issue + if: steps.author.outputs.skip != 'true' env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index fc9a52797c1d..5c83a8e691d7 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -2,9 +2,9 @@ name: typecheck on: push: - branches: [dev] + branches: [dev, v2] pull_request: - branches: [dev] + branches: [dev, v2] workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 006cab8c276c..ba80d79b90f0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ tmp dist ts-dist .turbo +.typecheck-profiles **/.serena .serena/ **/.omo diff --git a/.opencode/skills/opencode-drive/SKILL.md b/.opencode/skills/opencode-drive/SKILL.md new file mode 100644 index 000000000000..6633526b1091 --- /dev/null +++ b/.opencode/skills/opencode-drive/SKILL.md @@ -0,0 +1,254 @@ +--- +name: opencode-drive +description: Use when an agent needs drive OpenCode via a script or interact with an isolated instance +--- + +# OpenCode Drive + +Use `opencode-drive` to launch an isolated OpenCode instance and control it via commands or a script. + +There are two modes. Always default to using a script unless specifically directed to be interactive (connect +to an existing running instance, or start a new one, and make a few changes to the UI and read it, and iterate +on changes). + +Scripts allow you to run a full walkthrough in one run. When the script is done opencode-drive exits, +stops all processes, and cleans up all artifacts. + +# Prepare The Environment + +Use `init` when files must be added to the isolated home or project before OpenCode starts. It prints the artifact directory without launching OpenCode. A later `start` with the same name reuses it. + +```bash +artifacts=$(opencode-drive init --name demo) +cp -R ./fixtures/home/. "$artifacts/" +cp -R ./fixtures/project/. "$artifacts/files/" +opencode-drive start --name demo --dev ~/projects/opencode +``` + +The simulated project is under `$artifacts/files`. Running `start` without a prior `init` initializes the artifacts automatically. + +# Scripted usage + +You can write scripts that walk through entire flows, and gives you full access to controlling +the backend too. See examples of the script API at the bottom of this file. + +After creating or editing a script, always typecheck it before running. Never skip this step: + +```bash +opencode-drive check ./reproduce-stale-exploring-empty.ts +``` + +Run it by passing `--script` to start: + +```bash +opencode-drive start --name auto-stop-reproduction --script ./reproduce-stale-exploring-empty.ts +``` + +It will output information about the run, including paths to log files which you can read +to inspect what happened. If you need to dig into failures that aren't clear, read those log +files. If the script is unsuccessful, automatically fix the script and run it again. + +Scripts use one typed definition object. `setup` runs before OpenCode starts, +and `fs.writeFile` always writes inside the simulated project. + +You can read the full typed API here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/src/script/types.ts + +```ts +import { defineScript } from "opencode-drive" + +export default defineScript({ + async setup({ fs, config }) { + config.autoupdate = false + await fs.writeFile("src/example.ts", "export const value = 1\n") + }, + + async run({ ui, llm }) { + await ui.submit("Open src/example.ts") + await llm.send(llm.text("The file exports `value`.")) + await ui.waitFor("The file exports `value`.") + }, +}) +``` + +`setup` receives the current OpenCode config object, which starts from the +default drive config unless the prepared instance already has one. When a script +needs custom config, mutate this `config` parameter instead of generating and +writing a new config object from scratch, so the script keeps the default +provider/model settings unless it intentionally changes them. + +Note that the simulated model is a GPT model type, and opencode uses the `patch` tool for working with files Do not use a `edit` or `write` tool to edit files. + +Use `launch: "manual"` when the script needs to launch the server and every TUI +itself (this is extremely rare, do not use this unless explicitly asked). In this +mode `ui` is typed as `null`; call `server.launch()` exactly +once before launching clients. Each `clients.launch(name)` result provides the +same UI methods as the automatic client. You can see an example of this API +here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/multiple-clients.ts + +Use the exported `wait(milliseconds)` utility for an unconditional delay. + +`await llm.send(...)` waits for the next request and resolves after OpenCode +acknowledges its complete response. `llm.queue(...)` declares responses in +advance. Chunks may be built with `text`, `reasoning`, `toolCall`, `raw`, +`finish`, and `disconnect`. A normal response receives `finish("stop")` +automatically unless it yields or queues an explicit terminal event. + +`llm.text(text, { delay, chunkSize })` defaults to a 2 ms delay and a +15-character target varied by plus or minus 5 per chunk. + +`llm.reasoning` accepts the same options, and `llm.pause(milliseconds)` adds a +delay between any two outputs. + +Use `llm.serve` for an ongoing typed response generator: + +```ts +llm.serve(async function* (request, index) { + yield llm.reasoning(`Handling request ${index + 1}`) + yield llm.text(`Received ${request.id}`) + yield llm.finish("stop") +}) +``` + +The backend connection, response cleanup, cancellation, and recording +completion are automatic. + +You can see some example scripts here: + +- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/simple.ts +- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/serve.ts + +## Prune + +- `prune` removes artifact directories. These are always cleaned up after running a script + successfully, but leftover on failed runs. Always call this if a script fails. + +```bash +opencode-drive prune --name demo + +// --force cleans up all artifcat directories +opencode-dirve prune --force +``` + +# Live interaction usage + +- Always give headless instances a unique `--name`. Visible instances may omit it. +- A normal headless `start` detaches automatically and returns after the instance is ready. +- Do not add `&`; the long-running owner already runs in the background. +- Configure simulated model responses after startup when needed. +- Send ordered UI commands with `send`. +- Always stop the instance when finished. + +```bash +opencode-drive start --name demo + +opencode-drive send --name demo \ + --command.ui.type '{"text":"Explain this project"}' \ + --command.ui.enter + +opencode-drive stop --name demo +``` + +## Send UI Commands + +- Every `send` opens a connection to the named instance, runs its commands in order, and exits. +- Combine typing and Enter in one command when submitting a prompt. +- JSON-valued commands require one JSON argument. +- Multiple command flags execute from left to right. + +Commands: + +- `--command.ui.type ` types into the focused editor. Arguments: `text` string. +- `--command.ui.press ` presses a key. Arguments: `key` string; optional `modifiers` object with boolean `ctrl`, `shift`, `meta`, `super`, or `hyper`. +- `--command.ui.enter` presses Enter. Arguments: none. +- `--command.ui.arrow ` presses an arrow key. Arguments: `direction` is `up`, `down`, `left`, or `right`. +- `--command.ui.focus ` focuses an element. Arguments: `target` is the numeric element `num` returned by `ui.state`. +- `--command.ui.click ` clicks an element. Arguments: numeric `target`, `x`, and `y`; use the element `num` returned by `ui.state` as `target`. +- `--command.ui.state` prints focus and interactive element metadata as JSON. Arguments: none. +- `--command.ui.matches ` prints whether literal, case-sensitive text appears on screen. Arguments: `text` string. + +```bash +opencode-drive send --name demo \ + --command.ui.type '{"text":"Find the relevant code and explain it"}' \ + --command.ui.enter + +opencode-drive send --name demo \ + --command.ui.press '{"key":"p","modifiers":{"ctrl":true}}' + +opencode-drive send --name demo \ + --command.ui.arrow '{"direction":"down"}' + +opencode-drive send --name demo \ + --command.ui.focus '{"target":12}' + +opencode-drive send --name demo \ + --command.ui.click '{"target":12,"x":4,"y":1}' + +opencode-drive send --name demo \ + --command.ui.matches '{"text":"OpenCode"}' +``` + +To read the UI state and see information about interactable elements, use the `ui.state` command: + +```bash +opencode-drive send --name demo --command.ui.state +``` + +## Configure LLM Responses + +- `responses` controls what the LLM responds with +- Only use this if you are wanting to reproduce an exact type of response +- Defaults are `text,reasoning,diff,tool` with `write,apply_patch`. +- Supported types are `text`, `reasoning`, `diff`, and `tool`. +- `--tools` limits generated tool calls to names offered by OpenCode. + +```bash +opencode-drive responses --name demo \ + --types text,reasoning,diff,tool \ + --tools write,apply_patch + +opencode-drive responses --name demo \ + --types tool \ + --tools read,glob,grep +``` + +## Inspect The UI + +- `ui.state` prints focus and interactive element metadata as JSON. +- `ui.matches` checks for literal, case-sensitive screen text. +- `screenshot` prints the generated image path. + +```bash +opencode-drive screenshot --name demo +``` + +## Lifecycle + +- `stop` waits for recording export and owner cleanup before returning. + +```bash +opencode-drive stop --name demo +``` + +# Record The UI + +- Start with `--record` to capture a headless instance from its first rendered frame. +- `stop` finishes the recording, exports an MP4, and prints its path. + +```bash +opencode-drive start --name demo --record + +opencode-drive send --name demo \ + --command.ui.type '{"text":"Show me the current architecture"}' \ + --command.ui.enter + +opencode-drive stop --name demo +``` + +# Artifacts dir + +- `dir` prints the artifact directory for the instance. + +```bash +opencode-drive dir --name demo +``` + diff --git a/.opencode/skills/sample-skill/SKILL.md b/.opencode/skills/sample-skill/SKILL.md new file mode 100644 index 000000000000..87b9e9690798 --- /dev/null +++ b/.opencode/skills/sample-skill/SKILL.md @@ -0,0 +1,31 @@ +--- +name: sample-skill +description: Use when the user says sample skill, skill demo, or asks how an opencode SKILL.md should be structured; demonstrates a tiny project-local skill with practical assistant workflow guidance. +--- + +# Sample Skill + +This is a minimal project-local opencode skill. It exists as a reference for how a skill is structured and as a tiny reusable workflow the assistant can load when the user asks for a skill example. + +## When To Use + +- Use when the user asks for a sample skill or skill template. +- Use when demonstrating the required `SKILL.md` frontmatter and body format. +- Do not use for unrelated coding tasks just because a skill exists. + +## Workflow + +- Confirm the specific outcome the user wants if the request is ambiguous. +- Inspect the relevant files before changing anything. +- Make the smallest correct change. +- Verify the result with a focused read, typecheck, test, or other lightweight check when available. +- Summarize the changed files and any required restart or reload step. + +## Example Response Style + +When this skill is relevant, keep responses direct and actionable: + +```text +I created a project-local skill at .opencode/skills/sample-skill/SKILL.md. +Restart opencode for the new skill to be discovered by future sessions. +``` diff --git a/AGENTS.md b/AGENTS.md index cd2327e88811..f6f3c970e887 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,7 @@ - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. +- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required. - The default branch in this repo is `dev`. - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. @@ -18,12 +19,15 @@ Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes a Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`. +Never bypass Git hooks. Do not use `--no-verify` or otherwise disable, skip, or circumvent commit or push hooks. If a hook fails, fix the failure or stop and report it to the user. + ## Style Guide ### General Principles - Keep things in one function unless composable or reusable - Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller. +- Before adding complexity for a speculative or vanishingly unlikely race or security edge case, explain the concrete failure mode, likelihood, and complexity cost to the user and get their buy-in. Do not silently expand scope for theoretical robustness. - Avoid `try`/`catch` where possible - Avoid using the `any` type - Use Bun APIs when possible, like `Bun.file()` @@ -150,12 +154,15 @@ const table = sqliteTable("session", { ## V2 Session Core -- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries. -- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry. -- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op. +- Keep durable events minimal: record irreducible new facts and do not repeat state derivable by folding the ordered aggregate history. Enrich projections and read models with previous or derived state when consumers need self-contained views. +- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_pending` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries, consuming the pending row in the same event transaction; `session_pending` stores only unconsumed work. +- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Retry of an already-promoted input reconciles against the projected message and the durable admitted event rather than a retained row. +- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; interruption of a known but idle or locally unowned Session is a no-op, while the public API rejects an unknown Session. - Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics. -- Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop. +- Preserve one explicit `llm.stream(request)` call per Physical Attempt and reload projected history before durable continuation. Most Steps have one Physical Attempt; overflow-triggered compaction recovery may rebuild one Step for a second attempt. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop. - Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary. -- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once. +- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe step boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's step allowance; a batch of steers resets it once. +- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle. - Keep EventV2 replay owner claims separate from clustered Session execution ownership. -- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned. +- Keep the Instructions algebra and built-ins in `src/instructions`; keep instruction producers with their observed domains, and keep Session History selection plus `InstructionState` and `InstructionEntry` persistence Session-owned. `InstructionDiscovery` observes ambient global and upward-project instructions. The runner composes built-ins, discovery, guidance, and entries explicitly in `loadInstructions`; there is no instruction registry. +- `session.instructions.updated` stores only changed source keys and content hashes. Blob values live once in `instruction_blob`; `instruction_state` is a rebuildable fold cache, never primary state. Render initial instructions and chronological updates from values during request assembly. Completed compaction moves the instruction epoch; Session movement and committed revert clear it. Unavailable sources retain the last value and block only the initial complete delta. diff --git a/CONTEXT.md b/CONTEXT.md index 5e5955d344a1..79611f30f5de 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -4,40 +4,50 @@ OpenCode sessions preserve durable conversational history while assembling the r ## Language -**System Context**: -The structured collection of contextual facts presented to the model as initial instructions and chronological updates. -_Avoid_: System prompt +**Model Context**: +The complete model-visible input assembled for one **Step**, including system instructions, **Session History**, tool definitions, and step-local additions. **Instructions** are one component of Model Context, not a synonym for it. +_Avoid_: System Context + +**Instructions**: +The opaque algebra of independently refreshable typed instruction sources that render the durable instruction baseline and chronological updates shown to the model. +_Avoid_: Model Context, System Context **Session History**: -The projected chronological conversation selected for a provider turn after applying the active compaction and **Context Epoch** cutoffs. +The projected chronological conversation selected for a **Step** after applying the active compaction boundary and interleaving derived **Instruction Updates** from the current **Instruction Epoch**. _Avoid_: Session Context -**Context Source**: -One independently observed typed value within the **System Context**, represented by a stable key, JSON codec, infallible loader, pure baseline/update renderers, and an optional removal renderer for dynamic sources. +**Instruction Source**: +One independently read typed value within **Instructions**, represented by a stable namespaced key, canonical JSON codec, pure first/changed renderers, and an optional removal renderer. _Avoid_: Prompt fragment -**System Context Registry**: -The Location-scoped registry of ordered, scoped producers that contribute to the current **System Context**. +**InstructionEntry**: +One API-managed, durable, per-Session instruction value. Its slash-free client key maps to the `api/` **Instruction Source** key. Entries deliberately render to the model as mechanism-neutral `` blocks: the model sees session context, not how it was attached. + +**InstructionDiscovery**: +The Location-scoped service that observes ambient global and upward-project `AGENTS.md` files as one ordered aggregate **Instruction Source**. -**Mid-Conversation System Message**: -A durable chronological instruction that tells the model the newly effective state of a changed **Context Source**. -_Avoid_: System update, system notification, raw text diff +**Instruction State**: +The Session-owned projection cache of one instruction log fold: epoch start, values at that start, current values, and the last folded sequence. It is rebuilt from durable events and never authors model-visible facts. -**Context Epoch**: -The span during which one initially rendered **System Context** remains the immutable provider-cache baseline, ending at completed compaction, Session movement, or an incompatible context transition that requires a fresh baseline. +**Instruction Update**: +A durable `session.instructions.updated` value delta admitted at a **Safe Step Boundary**. Its model-visible System text is rendered from stored values at request assembly and is never persisted verbatim. +_Avoid_: Correction, stored prose, raw text diff -**Baseline System Context**: -The full **System Context** rendered at the start of a **Context Epoch**. +**Initial Instructions**: +The deterministic instruction text rendered from values at the current **Instruction Epoch** start and sent as provider-cache prefix state until completed compaction moves the epoch or Session movement or committed revert resets it. _Avoid_: Live system prompt -**Context Snapshot**: -The overwriteable model-hidden JSON state used to compare each **Context Source** with the value last admitted to a provider turn. +**Instruction Epoch**: +The span between completed compactions. Its start is the last `session.compaction.ended` sequence, or the initial complete instruction delta when no prior epoch exists. -**Unavailable Context**: -An expected temporary inability to observe a **Context Source** value; the runtime retains its prior effective state and emits no update, or omits it until first successfully loaded. +**Instruction Values**: +The key-to-hash map produced by folding instruction deltas in durable sequence order. Hash bodies live once in the content-addressed instruction blob store. -**Safe Provider-Turn Boundary**: -The point immediately before a provider call, after durable input promotion and any required tool settlement, where context changes may be admitted chronologically. +**Unavailable Instruction Source**: +An expected temporary inability to read an **Instruction Source** value; the runtime retains its prior effective value and emits no update, while an unavailable source blocks the initial complete delta. + +**Safe Step Boundary**: +The point during Step preparation, after prior tool settlement and before durable input promotion, where instruction changes may be admitted chronologically. **Admitted Prompt**: A durable user input accepted into the Session inbox but not yet included in **Session History**. @@ -45,11 +55,24 @@ A durable user input accepted into the Session inbox but not yet included in **S **Prompt Promotion**: The durable transition that removes an **Admitted Prompt** from pending input and appends its user message to **Session History**. -**Provider Turn**: -One request to a model provider and the response projected from that request. +**Step**: +One logical LLM call spanning pre-flight instruction synchronization, input promotion, request build, and compaction check; the provider stream; and tool settlement. +_Avoid_: provider turn, turn (unqualified) + +**Physical Attempt**: +One actual provider request on the wire in service of a **Step**; most Steps have one Physical Attempt, while overflow-triggered compaction recovery may give one Step two. + +**Assistant Turn**: +A reserved name for the not-yet-modeled unit containing all **Steps** from prompt promotion until the assistant yields the floor; do not reify it until something durable needs it. + +**Settlement**: +The terminal transition for a unit of work: Step and tool settlement are durable, while drain and execution settlement are coordinator-observed. + +**Execution**: +One session-scoped coordinator busy period from first wake until idle. An Execution is process-local coordination rather than a durable domain entity. **Session Drain**: -One process-local execution span that promotes eligible input and runs required **Provider Turns** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary. +One process-local execution span that promotes eligible input and runs required **Steps** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary. **Model Tool Output**: The bounded projection of a Core-executed tool result persisted in Session history and replayed to the model. A tool may shape this projection semantically, but the Tool Registry enforces the final size limit. @@ -87,52 +110,49 @@ _Avoid_: Response envelope ## Relationships -- A **System Context** is an opaque carrier composed from zero or more **Context Sources**. -- **Session History** contains projected conversational messages and admitted **Mid-Conversation System Messages**; the active **Baseline System Context** remains separate provider-request state. -- The **System Context Registry** uses stable-keyed scoped contributions to assemble the current **System Context**; contributor removal naturally removes its sources at the next **Safe Provider-Turn Boundary**. -- A changed **Context Source** may produce one **Mid-Conversation System Message** containing its newly effective state. -- A **Mid-Conversation System Message** persists the exact combined rendered text sent to the model. -- The current **Context Snapshot** advances atomically with the corresponding durable **Mid-Conversation System Message**. -- A **Context Snapshot** stores one codec-encoded JSON value and, for removable dynamic sources, a pre-rendered removal message per stable **Context Source** key. -- Changes from multiple **Context Sources** admitted at one safe boundary combine into one **Mid-Conversation System Message**. -- Context changes are sampled and admitted lazily at a **Safe Provider-Turn Boundary**, never pushed asynchronously when their source changes. -- At a **Safe Provider-Turn Boundary**, newly promoted user input or settled tool results precede any combined **Mid-Conversation System Message**. +- **Instructions** is an opaque carrier composed from zero or more **Instruction Sources**. +- **Model Context** is broader than **Instructions**. For each **Step**, the runner assembles the selected agent or provider system text, **Initial Instructions**, **Session History**, available tools, and step-local additions into one model request. +- **Session History** persists conversational messages. The runner derives model-facing **Instruction Update** messages from value deltas and interleaves them by durable sequence; **Initial Instructions** remain separate provider-request state. +- The runner explicitly loads and combines instruction built-ins, **InstructionDiscovery**, selected-agent skill guidance, reference guidance, MCP guidance, and **InstructionEntry** values. There is no instruction registry. +- `Instructions.combine(...)` preserves caller order and rejects duplicate stable namespaced source keys. The runner loads its producers concurrently, then combines them in its fixed declared order. +- Each **Instruction Source** read returns one coherent typed value, explicit removal, or temporary unavailability. `Instructions.make(...)` hides the value type so differently typed sources compose uniformly; its canonical codec defines storage and hash equivalence, while pure renderers produce first, changed, and optional removal text. +- `Instructions.read(...)` reads every composed source concurrently and exactly once at the boundary. `Instructions.diff(...)` compares encoded-value hashes with current **Instruction Values** and returns one delta plus new blob bodies. +- `Instructions.renderInitial(...)` renders values at the **Instruction Epoch** start. `Instructions.renderUpdate(...)` renders one hydrated delta against the values immediately before it. +- A changed **Instruction Source** contributes its hash to one **Instruction Update**; explicit removal contributes the `"removed"` sentinel. +- An **Instruction Update** persists only its value delta. Rendered text is derived during request assembly and excluded from compaction summaries. +- The instruction blob insert, durable delta, and **Instruction State** advance commit atomically. +- Changes from multiple **Instruction Sources** admitted at one safe boundary combine into one **Instruction Update**. +- Instruction changes are sampled and admitted lazily at a **Safe Step Boundary**, never pushed asynchronously when their source changes. +- At a **Safe Step Boundary**, prior tool results are already settled; instruction preparation completes before newly admitted user input promotes. - An **Admitted Prompt** is replayable pending input, not yet model-visible **Session History**. - **Prompt Promotion** atomically consumes the pending inbox entry and appends its model-visible user message. -- Steering prompts promote at the next **Safe Provider-Turn Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's provider-turn allowance; multiple prompts promoted at one boundary reset it once. +- Steering prompts promote at the next **Safe Step Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's step allowance; multiple prompts promoted at one boundary reset it once. - A queued prompt does not promote while the current **Session Drain** requires continuation. The runner promotes one queued prompt when the Session would otherwise become idle, then reevaluates continuation before promoting another. -- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, provider attempts, and tool state rather than inventing an enclosing execution identity. -- The first provider turn renders the latest complete **Baseline System Context** and initializes its **Context Snapshot** without emitting a redundant **Mid-Conversation System Message**; unavailable initial context blocks the turn instead of persisting an incomplete baseline. -- Initial **System Context** preparation precedes the first durable input promotion so an unavailable baseline leaves that input pending and retryable; ordinary reconciliation remains after promotion. -- Compaction starts a new **Context Epoch** with a freshly rendered **Baseline System Context** and **Context Snapshot**; prior **Mid-Conversation System Messages** remain durable audit history but leave projected model history. -- A newly registered core or plugin-defined **Context Source** absent from the current snapshot emits its baseline rendering once at the next **Safe Provider-Turn Boundary**. -- **Context Source** keys are stable and namespaced; duplicate keys fail composition. `SystemContext.combine(...)` preserves caller order; the **System Context Registry** evaluates producers concurrently and combines them in stable contribution-key order so rendered context remains deterministic. -- Each **Context Source** loader returns one coherent typed value. `SystemContext.make(...)` hides that value type so differently typed sources compose uniformly. Its codec compares and stores that value; its pure renderers produce model-visible baseline, update, and removal text only when needed. -- `SystemContext.initialize(...)` observes a composed **System Context** once and produces a fresh **Baseline System Context** with its **Context Snapshot**. -- `SystemContext.reconcile(...)` observes a composed **System Context** once and returns exactly one next action: unchanged, updated, replacement ready, or replacement blocked. -- `SystemContext.replace(...)` renders a fresh generation after completed compaction or another baseline-replacing transition; it reports replacement blocked while previously admitted context is unavailable. -- **Unavailable Context** uses stale-while-revalidate semantics and is distinct from a successfully loaded absence, which may emit removal text. -- Ordinary **Context Source** loaders return values directly; loaders that intentionally use stale-while-revalidate may explicitly return **Unavailable Context**. -- Nested project instruction discovery after successful reads remains a follow-up; when implemented, discovered instructions must be admitted durably at the next **Safe Provider-Turn Boundary**. -- Location-scoped services naturally re-resolve effective context when a moved session next runs in its destination location. -- Moving a Session clears its active **Context Epoch**, so the destination must initialize a complete baseline before another prompt can promote. -- Instruction discovery, source identity, persistence, and file loading belong to the instruction service; the **System Context** abstraction only composes effectful producers and renders loaded values. -- The first instruction-service slice observes global and upward project `AGENTS.md` files as one ordered aggregate **Context Source** at each **Safe Provider-Turn Boundary**. -- Built-in and instruction context producers register through the **System Context Registry** with stable contribution keys. Plugin-defined context registration and hot-reload lifecycle remain a follow-up built on the same scoped registry seam. -- Selected-agent available-skill guidance is a **Context Source** composed with Location-wide registry sources immediately before Context Epoch admission. It lists only names and descriptions permitted for that agent; skill bodies and locations are exposed only through the permission-checked `skill` tool. -- The selected agent and model are sampled when a provider turn starts. Changes admitted after that boundary apply to the next provider turn and do not restart the current turn. -- Selected-agent available-skill guidance remains a **Context Source**. An agent switch that changes that guidance produces a **Mid-Conversation System Message** while preserving the current baseline. -- Local tool authorization and pending permission requests retain the effective agent of the provider turn that issued the call; a later agent switch cannot change that call's policy. -- Context source changes never wake idle sessions; the next naturally scheduled **Safe Provider-Turn Boundary** loads and compares current values lazily. -- Once admitted, a **Mid-Conversation System Message** remains durable even if the following provider attempt fails and is replayed unchanged on retry. -- **Mid-Conversation System Messages** remain durable Session-message history; normal user-facing transcript surfaces may hide them. -- The date **Context Source** initially preserves host-local calendar-date behavior; a configured user timezone may replace that default later. -- A **Context Epoch** begins with one immutable **Baseline System Context**. -- A **Baseline System Context** is stored durably and reused verbatim across process restarts within its **Context Epoch**. -- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix. -- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history. -- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn. -- **Native Continuation Metadata** remains in durable history. Provider-turn projection includes it only for a successful exact originating provider/model match; failed turns and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility. +- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, physical attempts, and tool state rather than inventing an enclosing execution identity. +- An **Execution** contains one or more **Session Drains**; a **Session Drain** contains one reserved assistant-turn span at a time; that span contains **Steps**; and each **Step** contains one or more **Physical Attempts** plus any tool calls it requires. +- A **Step** record covers only the model-visible span from first assistant output through tool settlement; pre-flight leaves no record, and one Step settles at most one record. +- The first **Step** admits one complete delta and renders **Initial Instructions** without narrating that delta in history; an unavailable initial source blocks the Step instead of persisting incomplete values. +- Instruction preparation precedes durable input promotion on every Step so an unavailable first baseline leaves pending input untouched and later updates enter history before newly promoted input. +- Completed compaction moves the **Instruction Epoch** to the exact `session.compaction.ended` sequence and copies current hashes to the epoch's initial values. Earlier updates leave active model history while durable deltas remain. +- A newly composed **Instruction Source** absent from current **Instruction Values** emits its first rendering once at the next **Safe Step Boundary**. +- **Unavailable Instruction Source** uses stale-while-revalidate semantics and is distinct from a successfully loaded absence, which may emit removal text. +- **InstructionDiscovery** observes ambient instructions as one ordered aggregate **Instruction Source**. +- Ambient discovery reads global and upward-project `AGENTS.md` files and honors `OPENCODE_DISABLE_PROJECT_CONFIG` for project files. +- After a successful internal file or directory read, nearby `AGENTS.md` files toward the Location root are injected once per Session as durable synthetic instruction messages. +- **InstructionEntry** stores API-managed per-Session JSON values. Each entry contributes one `api/` **Instruction Source**, so adding, replacing, or removing an entry is reconciled at the next **Safe Step Boundary**. +- Location-scoped instruction producers naturally re-resolve when a moved Session next runs in its destination Location. +- Moving a Session clears its **Instruction State**, so the destination must admit a complete delta before another prompt can promote. Committed revert does the same; replay derives both resets from their durable events. +- Selected-agent available-skill guidance is an **Instruction Source** composed explicitly by the runner. It lists only names and descriptions permitted for that agent; skill bodies and locations are exposed only through the permission-checked `skill` tool. +- The selected agent and model are sampled when a **Step** starts. Changes admitted after that boundary apply to the next Step and do not restart the current Step. +- An agent switch that changes selected-agent guidance produces an **Instruction Update** while preserving the current baseline. +- Local tool authorization and pending permission requests retain the effective agent of the **Step** that issued the call; a later agent switch cannot change that call's policy. +- Instruction source changes never wake idle Sessions; the next naturally scheduled **Safe Step Boundary** loads and compares current values lazily. +- Once admitted, an **Instruction Update** remains durable even if the following **Physical Attempt** fails and is replayed unchanged on retry. +- **Instruction Updates** remain durable value history but are not `session_message` rows. Clients display changed keys rather than model-facing prose. +- The date **Instruction Source** initially preserves host-local calendar-date behavior; a configured user timezone may replace that default later. +- **Initial Instructions** are recomputed deterministically from durable values for every request; rendered bytes are not stored. +- A model/provider switch preserves current **Instruction Values**, the **Instruction Epoch**, and chronological conversation history; the new selection applies to the next **Step**. +- **Native Continuation Metadata** remains in durable history. Step projection includes it only for a successful exact originating provider/model match; failed Steps and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility. - **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding. - **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing. - The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`. @@ -162,12 +182,12 @@ _Avoid_: Response envelope - SDK executes Server's assembled `HttpRouter` in memory. It opens no listener and performs no network I/O, while preserving Server routing, middleware, codecs, handlers, and errors. - The Effect Client and SDK re-export their decoded datatype facade from Schema so callers do not depend on internal package locations or Core's versioned names. - A capability intended for both networked and **Embedded OpenCode** belongs in the authoritative public `HttpApi`; embedded-only same-process capabilities extend **Embedded OpenCode** separately. -- `sessions.events({ sessionID, after })` is a public durable Session event stream. It verifies the Session, replays durable events after the optional aggregate sequence, continues with newly committed durable events, excludes live-only fragments, and is transported as SSE in both networked and embedded modes. +- `sessions.log({ sessionID, after, follow })` is the public durable Session event stream. It verifies the Session, replays durable events after the optional aggregate sequence, optionally continues with newly committed durable events, excludes live-only fragments, and is transported as SSE in both networked and embedded modes. - `events.subscribe()` is a distinct public instance-wide live stream for Session and non-Session activity. It has no replay guarantee and includes connection, heartbeat, and instance-disposal lifecycle events; consumers recover from disconnection by refreshing authoritative state. - A Session ID is not an optional filter on `events.subscribe()`: instance-wide live events and durable Session events have different schemas, replay guarantees, cursors, lifecycle events, and failure behavior. - The initial common OpenCode Client does not expose server-global event aggregation. `events.subscribe()` is bounded to the connected OpenCode instance or workspace; any future cross-instance administrative stream requires a separately designed API. - `events.subscribe()` does not automatically reconnect after transport loss. The live-only stream fails with `ClientError`; consumers refresh authoritative state before explicitly opening a new subscription because events missed during disconnection cannot be replayed. -- `sessions.events({ sessionID, after })` returns the generated HTTP client's cold durable event stream and does not build reconnection policy into the endpoint or client constructor. Transport loss fails the stream with `ClientError`. Callers may compose an explicit resuming stream above it by retaining the last observed durable sequence and opening a new subscription with `after`; any reusable resume helper remains a separate API design question. +- `sessions.log({ sessionID, after, follow })` returns the generated HTTP client's cold durable event stream and does not build reconnection policy into the endpoint or client constructor. Transport loss fails the stream with `ClientError`. Callers may compose an explicit resuming stream above it by retaining the last observed durable sequence and opening a new subscription with `after`; any reusable resume helper remains a separate API design question. - The stable `sessions.list(...)` design returns a **Page** in both networked and **Embedded OpenCode**; embedded execution does not define a separate unbounded array-returning list operation. The beta client currently preserves the existing HTTP `{ data, cursor }` envelope until emitter-level Page projection is implemented. - Session list cursors are opaque branded values carrying continuation query and ordering state. Consumers pass them back unchanged and do not inspect storage anchors or encoded filter fields. - A Session list continuation accepts only its opaque cursor. Scope, filters, ordering, and page size are fixed by the initial query and carried by that cursor. @@ -175,27 +195,27 @@ _Avoid_: Response envelope - `sessions.message({ sessionID, messageID })` is a required resource lookup. An unknown Session fails with `SessionNotFoundError`; a known Session with an absent or differently owned message fails with `MessageNotFoundError` without disclosing cross-Session ownership. Absence is not represented as `undefined` across the public HTTP boundary. - `sessions.interrupt({ sessionID })` first verifies that the durable Session exists, failing with `SessionNotFoundError` otherwise. For a known Session, interruption is idempotent: idle, already-settled, or locally unowned execution is a no-op. - `sessions.active()` snapshots the current process's foreground Session drain registry as a record of Session IDs to `{ type: "running" }`. Missing IDs are inactive; background subagents and tasks do not make their parent Session active, and process restart clears the registry. -- `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected conversational messages selected as Session context; it does not include or represent the complete provider request context, whose baseline system context and other contributions remain separate. -- **Open question**: Should a future, separately named operation expose the complete provider request context, including baseline system context, selected source contributions, and context-epoch metadata? +- `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected **Session History**; it does not include or represent the complete **Model Context**, whose agent system text, **Initial Instructions**, tools, and step-local additions remain separate. +- **Open question**: Should a future, separately named operation expose complete **Model Context**, including the instruction baseline, applied instruction metadata, tools, and step-local additions? - `sessions.prompt(...)` exposes `resume?: boolean`. Omitting it preserves durable admission followed by an advisory execution wake; `resume: false` requests durable admit-only behavior. -- The public operation remains `sessions.prompt(...)`; `SessionInput.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics. +- The public operation remains `sessions.prompt(...)`; `SessionPending.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics. - `sessions.create(...)` accepts an optional `location`. Omission resolves through the connected OpenCode instance's default or current location; an explicit value selects a known location. Networked and embedded transports use the same handler semantics. - `sessions.switchAgent({ sessionID, agent })` is part of the common client alongside `sessions.switchModel(...)`. It affects subsequent Session activity and fails with `SessionNotFoundError` for an unknown Session. - The **Embedded OpenCode** Layer delegates to the same scoped creation path; it does not define a second implementation. - A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter. -- A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise. -- When the effective aggregate instruction set changes, its **Mid-Conversation System Message** includes the complete current ordered set and supersedes the prior aggregate value; when no ambient instructions remain, the message states that previously loaded instructions no longer apply. +- An **Instruction Update** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise. +- When the aggregate discovered instruction set changes, its **Instruction Update** includes the complete current ordered set and supersedes the prior aggregate value; when no discovered instructions remain, the message states that previously loaded instructions no longer apply. - Ambient project instruction discovery honors `OPENCODE_DISABLE_PROJECT_CONFIG`; global instructions remain eligible. - Oversized textual **Model Tool Output** retains a bounded preview in Session history while its complete text moves to managed tool-output storage. Arbitrary structured-result size is a separate concern. - One tool settlement receives one aggregate textual limit, using the configured maximum lines or UTF-8 bytes, whichever is reached first. The limit is provider-independent; token pressure belongs to context assembly and compaction. - Generic truncation preserves the beginning and end of textual output. Tools may apply a more meaningful strategy before the Tool Registry enforces the final limit. -- A truncated **Model Tool Output** identifies its complete text both in the bounded model-visible preview and as a typed managed output path. Managed output paths do not modify the tool's validated structured result. +- A truncated **Model Tool Output** identifies its complete text in the bounded model-visible preview. The Tool Registry also supplies managed paths as internal metadata to tool hooks; Session events do not expose a typed `outputPaths` field. - A **Managed Tool Output File** is temporary and may expire after its retention period. The bounded **Model Tool Output**, not the file, is the durable replayable record. -- Failure to retain a **Managed Tool Output File** does not change a successful tool operation into a failed one. The Session records an explicitly lossy bounded output without a path, while operators receive diagnostics for the storage failure. +- Failure to retain a **Managed Tool Output File** fails settlement operationally. The Session never publishes a successful result whose complete output was lost during generic bounding. - Once a tool operation succeeds, bounding its **Model Tool Output** and publishing its one durable settlement form an interruption-safe completion region. Raw oversized success is never published before a later correction. - When a structured-only result would exceed the **Model Tool Output** limit, its validated structured value remains unchanged for Session consumers while model replay uses a bounded textual JSON preview and optional managed output path. - Existing tool-managed output paths survive generic bounding. A fallback file retains exactly the complete projected text received by the Tool Registry and never claims to reconstruct output already discarded by tool-specific shaping. -- **Managed Tool Output Files** use globally unique names in one shared flat directory. Their absolute paths are readable and searchable by ordinary tools; other absolute paths remain outside Location-scoped filesystem authority. +- **Managed Tool Output Files** use globally unique names in one shared flat directory. They receive no special filesystem authority; each tool applies its ordinary external-path policy. - Provider-executed tool results remain provider-native transcript facts outside generic Tool Registry bounding. Their context control requires provider-aware pruning or compaction because some providers require exact structured round-trip payloads. ## Client contract architecture @@ -217,9 +237,9 @@ Before stabilizing the client API: ## Example dialogue -> **Dev:** "The date changed while the session was active. Should the **Mid-Conversation System Message** say what the old date was?" -> **Domain expert:** "No. Emit the newly effective date so the agent can act on the current **System Context**." +> **Dev:** "The date changed while the session was active. Should the **Instruction Update** say what the old date was?" +> **Domain expert:** "No. Emit the newly effective date so the agent can act on the current instructions." ## Flagged ambiguities -- Legacy `experimental.chat.system.transform` can mutate the assembled baseline system prompt arbitrarily, but V2 plugins do not yet expose an equivalent hook. Decide separately whether to port it, replace dynamic uses with plugin-defined **Context Sources**, or narrow its semantics. +- Legacy `experimental.chat.system.transform` can mutate assembled system text arbitrarily, but V2 plugins do not yet expose an equivalent hook. Decide separately whether to port it, model dynamic uses as explicit **Instruction Sources**, or narrow its semantics. diff --git a/README.zh.md b/README.zh.md index 93f81e83cd0c..352e13ccaa1d 100644 --- a/README.zh.md +++ b/README.zh.md @@ -125,4 +125,4 @@ OpenCode 内置两种 Agent,可用 `Tab` 键快速切换: --- -**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=738j8655-cd59-4633-a30a-1124e0096789&qr_code=true) | [X.com](https://x.com/opencode) +**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=52ao9352-5623-4fa0-b7dd-3407c392c1af&qr_code=true) | [X.com](https://x.com/opencode) diff --git a/README.zht.md b/README.zht.md index 59d9709bd39f..f09358b1330b 100644 --- a/README.zht.md +++ b/README.zht.md @@ -125,4 +125,4 @@ OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。 --- -**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=738j8655-cd59-4633-a30a-1124e0096789&qr_code=true) | [X.com](https://x.com/opencode) +**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=52ao9352-5623-4fa0-b7dd-3407c392c1af&qr_code=true) | [X.com](https://x.com/opencode) diff --git a/artifacts/glm52-rise-video/.gitignore b/artifacts/glm52-rise-video/.gitignore new file mode 100644 index 000000000000..9d38bb25911c --- /dev/null +++ b/artifacts/glm52-rise-video/.gitignore @@ -0,0 +1,3 @@ +node_modules +.remotion +out/frame-*.png diff --git a/artifacts/glm52-rise-video/bun.lock b/artifacts/glm52-rise-video/bun.lock new file mode 100644 index 000000000000..cac009ae812f --- /dev/null +++ b/artifacts/glm52-rise-video/bun.lock @@ -0,0 +1,483 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "glm52-rise-video", + "dependencies": { + "@remotion/cli": "^4.0.384", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "remotion": "^4.0.384", + }, + "devDependencies": { + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "typescript": "^5.8.2", + }, + }, + }, + "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.24.1", "", { "bin": "./bin/babel-parser.js" }, "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg=="], + + "@babel/types": ["@babel/types@7.24.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w=="], + + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mediabunny/aac-encoder": ["@mediabunny/aac-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-JNzgdJoHMFFnv5imi1+dmjZMudsJ1zNCUCEkKjBh90cqGhAFg8xu4V2gsyxE2i6oq/YpWx23P+OvoANLSMcDzA=="], + + "@mediabunny/flac-encoder": ["@mediabunny/flac-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-VpKmJO0xlYcFCRD6JvJlMbNQ6d/6YMHdO1gFIqWlZABHjSSL6BquNNEgWSCv5vdF8ELDBwIYBVZEslakIB/7GA=="], + + "@mediabunny/mp3-encoder": ["@mediabunny/mp3-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-JyzZyGeGRm2HVUQaGJ/VZT9OG+kG00mA8SLP/f3CO7+qWAmBncKc16WYXWHbZEo8Jn/ZGA6De32S5R2bTQbDqA=="], + + "@module-federation/error-codes": ["@module-federation/error-codes@0.22.0", "", {}, "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug=="], + + "@module-federation/runtime": ["@module-federation/runtime@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/runtime-core": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA=="], + + "@module-federation/runtime-core": ["@module-federation/runtime-core@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA=="], + + "@module-federation/runtime-tools": ["@module-federation/runtime-tools@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/webpack-bundler-runtime": "0.22.0" } }, "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA=="], + + "@module-federation/sdk": ["@module-federation/sdk@0.22.0", "", {}, "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g=="], + + "@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + + "@remotion/bundler": ["@remotion/bundler@4.0.483", "", { "dependencies": { "@remotion/media-parser": "4.0.483", "@remotion/studio": "4.0.483", "@remotion/studio-shared": "4.0.483", "@remotion/timeline-utils": "4.0.483", "@rspack/core": "1.7.11", "@rspack/plugin-react-refresh": "1.6.1", "css-loader": "7.1.4", "esbuild": "0.28.1", "react-refresh": "0.18.0", "remotion": "4.0.483", "style-loader": "4.0.0", "webpack": "5.105.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-5fQRMYgr2sxZPBOThnIVuFwHBq/FhusJ+Ke6xopga8io8ecK/mj9GaRXC3tgfMa+YWuu8ZSXZ0U9EsMZjjP5YQ=="], + + "@remotion/canvas-capture": ["@remotion/canvas-capture@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JABgbfvXwjFp1E61tlDN9gVAH8OoeF4kUUELNwG9kcNOE4ex8+vCYEO6VKIaXZ4l8A1PEAQ5EK//NheTJ80gfw=="], + + "@remotion/cli": ["@remotion/cli@4.0.483", "", { "dependencies": { "@remotion/bundler": "4.0.483", "@remotion/media-utils": "4.0.483", "@remotion/player": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio": "4.0.483", "@remotion/studio-server": "4.0.483", "@remotion/studio-shared": "4.0.483", "dotenv": "17.3.1", "minimist": "1.2.6", "prompts": "2.4.2", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" }, "bin": { "remotion": "remotion-cli.js", "remotionb": "remotionb-cli.js", "remotiond": "remotiond-cli.js" } }, "sha512-z4TQcgTfQbSIV4GQpCvIihDL77M+leQelbxv962PmfSKTT7z3XnNN732PcZ4sei2+Xg+d2SD95K4tI5XBYs5Gw=="], + + "@remotion/compositor-darwin-arm64": ["@remotion/compositor-darwin-arm64@4.0.483", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CkEJXouGEoCs5PX3KOufkXrlT/Kgn0ZF7SEQ4meCYGYMZD+QPVtsmS1MXH8pQ4RHlFO6Tk7g0CIHMT3Y4yzpuA=="], + + "@remotion/compositor-darwin-x64": ["@remotion/compositor-darwin-x64@4.0.483", "", { "os": "darwin", "cpu": "x64" }, "sha512-vDZK5U8FYbHGMjFC2lUgJRBz8KXwbwFSvEQi8FATD0roNg75SoaEJWrqAOIVv8v5eBjPvo18znDc36NLRYn1Eg=="], + + "@remotion/compositor-linux-arm64-gnu": ["@remotion/compositor-linux-arm64-gnu@4.0.483", "", { "os": "linux", "cpu": "arm64" }, "sha512-1LzfYDY/DQxy3MIc9pCbTn1ObcBDcyI6MzODOwe1bTZl8TZxdUUt1M3hDrQ60AB6iHwruBAnzT9dnogiMIdSew=="], + + "@remotion/compositor-linux-arm64-musl": ["@remotion/compositor-linux-arm64-musl@4.0.483", "", { "os": "linux", "cpu": "arm64" }, "sha512-nG1btxg0HXuLzulIqAg9NnxWyewZNrA0iLe82HdniaJjeWLORreGo4cEEJz1SjbDpQuPEY562XGkTO2DLKK5mA=="], + + "@remotion/compositor-linux-x64-gnu": ["@remotion/compositor-linux-x64-gnu@4.0.483", "", { "os": "linux", "cpu": "x64" }, "sha512-CV4h6r2rNYh4P8utUzP6RM2vW2vq43nA3IvVOwRSuh9LwJh/7rwG53Oco4HanY/sstaNuymGpT57wdXwONU5CQ=="], + + "@remotion/compositor-linux-x64-musl": ["@remotion/compositor-linux-x64-musl@4.0.483", "", { "os": "linux", "cpu": "x64" }, "sha512-moKvavbkoz5nteJZA4K6LMgTTlewhwIbtO7DYARZ3LK3ClGUKnYfFcmaPS15hMun02JPTOMIWbrlaszdR75mxA=="], + + "@remotion/compositor-win32-x64-msvc": ["@remotion/compositor-win32-x64-msvc@4.0.483", "", { "os": "win32", "cpu": "x64" }, "sha512-eDpWe6Vy7ySHugwxlgaeohqdBwR1etxiymXzT13cgbPw2+p6d55z1tJ7VPTXyfu0o+UdyRTauWqD4msoxN0wow=="], + + "@remotion/licensing": ["@remotion/licensing@4.0.483", "", {}, "sha512-GRtjykrxMmB5Cjpq1oVqeYIRXI95vid6afX0LTLfp1shD6MR4lTZRoRuopxrrZp1E4XFGXoY64b6dlIudgTyBQ=="], + + "@remotion/media-parser": ["@remotion/media-parser@4.0.483", "", {}, "sha512-iva9Eof7QQX+3hGxH/N8pGi80wgdGuzOr825Zn7IuEe7IWdtRYWlFpCEghmyfhrwgaWa7RY51wg77ycR3c3nRg=="], + + "@remotion/media-utils": ["@remotion/media-utils@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4T4WQh/U95kCBzgmz/EL/HL8Zeox/AKAL5ufsehrZFgwlcvH1EsKNJH/RWaAtNPRLF+pskEOgFzPYaczHeI8hg=="], + + "@remotion/player": ["@remotion/player@4.0.483", "", { "dependencies": { "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yyIQ9vinUqqS0fQeL52bPCWQIr8qDXCqQ4iys8P7dSZncdu8V29p4W2a0/4eKV8bv5+mjnJQ/U6nl9NxBF1hdA=="], + + "@remotion/renderer": ["@remotion/renderer@4.0.483", "", { "dependencies": { "@remotion/licensing": "4.0.483", "@remotion/streaming": "4.0.483", "execa": "5.1.1", "remotion": "4.0.483", "source-map": "0.8.0-beta.0", "ws": "8.21.0" }, "optionalDependencies": { "@remotion/compositor-darwin-arm64": "4.0.483", "@remotion/compositor-darwin-x64": "4.0.483", "@remotion/compositor-linux-arm64-gnu": "4.0.483", "@remotion/compositor-linux-arm64-musl": "4.0.483", "@remotion/compositor-linux-x64-gnu": "4.0.483", "@remotion/compositor-linux-x64-musl": "4.0.483", "@remotion/compositor-win32-x64-msvc": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-eSKdQqm8rcl+GNyOhFsBgcPYWMcxTJWHL5xaNrd037piCaL3sdJ57OEO/AbfEvvOO14zwUvlsONKjlkLlEVyKg=="], + + "@remotion/streaming": ["@remotion/streaming@4.0.483", "", {}, "sha512-96rqrk+l5AfriHOqmqxMn8rI6UJfFY7r5UMDZoSKqXWzTOwj+0VWHTRl9ilZQMXhYACaMSA/5yrL9jgQEK8Yuw=="], + + "@remotion/studio": ["@remotion/studio@4.0.483", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "@remotion/canvas-capture": "4.0.483", "@remotion/media-utils": "4.0.483", "@remotion/player": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio-shared": "4.0.483", "@remotion/timeline-utils": "4.0.483", "@remotion/web-renderer": "4.0.483", "@remotion/zod-types": "4.0.483", "mediabunny": "1.47.0", "memfs": "3.4.3", "open": "8.4.2", "remotion": "4.0.483", "semver": "7.5.3", "zod": "4.3.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-gNa1Lw8NmHl0jwFb5fk7rzA+WFTJQpGDT/49A38t9jpEwoF+h1mKTgY8gA202apzjBHrEECdFE450npnBOUpUw=="], + + "@remotion/studio-server": ["@remotion/studio-server@4.0.483", "", { "dependencies": { "@babel/parser": "7.24.1", "@babel/types": "7.24.0", "@remotion/bundler": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio-shared": "4.0.483", "memfs": "3.4.3", "open": "8.4.2", "prettier": "3.8.1", "recast": "0.23.11", "remotion": "4.0.483", "semver": "7.5.3" } }, "sha512-2FSLbVN5N8bgtQPX+RQRbis557tJJnVTOZuEBZndfC9NOoPdARlSgEnwVer9tBNczUlc6B3KrPxyPSlS3UjL+Q=="], + + "@remotion/studio-shared": ["@remotion/studio-shared@4.0.483", "", { "dependencies": { "remotion": "4.0.483" } }, "sha512-aXxBYUgsWgphHVq7T7bz+ICMGzfii7pcX1IxS5hbLMLJHqlDtk9g+qcxm4kJ2Fjj/Gb5gERKiNzYym8WZTIM2Q=="], + + "@remotion/timeline-utils": ["@remotion/timeline-utils@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0" } }, "sha512-KTTMdpNA5uRLX+iisAITO+V9cJBk17F8EWvuUnAEVmAFOQTbGIyPI9HNw5pZmL7SlzOJdFjAtksTjipeYGkEug=="], + + "@remotion/web-renderer": ["@remotion/web-renderer@4.0.483", "", { "dependencies": { "@mediabunny/aac-encoder": "1.47.0", "@mediabunny/flac-encoder": "1.47.0", "@mediabunny/mp3-encoder": "1.47.0", "@remotion/licensing": "4.0.483", "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-14iURfVWA/hcF/rJvkid1ZR5nmnVhkTIf071FabCFi6kdViOSVBugSglJ8tUPvWVbWmxlXGDGQD9CSU18IATww=="], + + "@remotion/zod-types": ["@remotion/zod-types@4.0.483", "", { "dependencies": { "remotion": "4.0.483" }, "peerDependencies": { "zod": "4.3.6" } }, "sha512-KIVYIzFpZBDB0wxGGQu5tDBr4XOYRzvrPXjmo6O/FI5Xx7O8hgvJmBs9VDkEwonAOft86sg79hI19F1DTX1p4w=="], + + "@rspack/binding": ["@rspack/binding@1.7.11", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "1.7.11", "@rspack/binding-darwin-x64": "1.7.11", "@rspack/binding-linux-arm64-gnu": "1.7.11", "@rspack/binding-linux-arm64-musl": "1.7.11", "@rspack/binding-linux-x64-gnu": "1.7.11", "@rspack/binding-linux-x64-musl": "1.7.11", "@rspack/binding-wasm32-wasi": "1.7.11", "@rspack/binding-win32-arm64-msvc": "1.7.11", "@rspack/binding-win32-ia32-msvc": "1.7.11", "@rspack/binding-win32-x64-msvc": "1.7.11" } }, "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q=="], + + "@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@1.7.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig=="], + + "@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@1.7.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA=="], + + "@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@1.7.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA=="], + + "@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@1.7.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw=="], + + "@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@1.7.11", "", { "os": "linux", "cpu": "x64" }, "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ=="], + + "@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@1.7.11", "", { "os": "linux", "cpu": "x64" }, "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg=="], + + "@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@1.7.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "1.0.7" }, "cpu": "none" }, "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ=="], + + "@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@1.7.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA=="], + + "@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@1.7.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw=="], + + "@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@1.7.11", "", { "os": "win32", "cpu": "x64" }, "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q=="], + + "@rspack/core": ["@rspack/core@1.7.11", "", { "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.11", "@rspack/lite-tapable": "1.1.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" }, "optionalPeers": ["@swc/helpers"] }, "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew=="], + + "@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.0", "", {}, "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw=="], + + "@rspack/plugin-react-refresh": ["@rspack/plugin-react-refresh@1.6.1", "", { "dependencies": { "error-stack-parser": "^2.1.4", "html-entities": "^2.6.0" }, "peerDependencies": { "react-refresh": ">=0.10.0 <1.0.0", "webpack-hot-middleware": "2.x" }, "optionalPeers": ["webpack-hot-middleware"] }, "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="], + + "@types/dom-webcodecs": ["@types/dom-webcodecs@0.1.13", "", {}, "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ=="], + + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], + + "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.40", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw=="], + + "browserslist": ["browserslist@4.28.4", "", { "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", "electron-to-chromium": "^1.5.376", "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-loader": ["css-loader@7.1.4", "", { "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.6.3" }, "peerDependencies": { "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.379", "", {}, "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA=="], + + "enhanced-resolve": ["enhanced-resolve@5.24.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw=="], + + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + + "fs-monkey": ["fs-monkey@1.0.3", "", {}, "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="], + + "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "mediabunny": ["mediabunny@1.47.0", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-XQMZAcaKPkJ7hQ/Q2fvBdl3ZazQl2WVxDysUbJWh4PuAnLoerdsQBdPTDWdUdK6hh26LQ8Ue94MLLnmpWvlUYg=="], + + "memfs": ["memfs@3.4.3", "", { "dependencies": { "fs-monkey": "1.0.3" } }, "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimist": ["minimist@1.2.6", "", {}, "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="], + + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-releases": ["node-releases@2.0.50", "", {}, "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "postcss-modules-extract-imports": ["postcss-modules-extract-imports@3.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q=="], + + "postcss-modules-local-by-default": ["postcss-modules-local-by-default@4.2.0", "", { "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw=="], + + "postcss-modules-scope": ["postcss-modules-scope@3.2.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA=="], + + "postcss-modules-values": ["postcss-modules-values@4.0.0", "", { "dependencies": { "icss-utils": "^5.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], + + "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + + "remotion": ["remotion@4.0.483", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-lf4xq4Twn75TQeTavFLrTE4zdck7EKBSx07ZrPv4om40Sist+3G0kq7NNzSjMeJ6G2Wx6vI+0oICD/vv4sGWtg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "semver": ["semver@7.5.3", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "style-loader": ["style-loader@4.0.0", "", { "peerDependencies": { "webpack": "^5.27.0" } }, "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "terser": ["terser@5.48.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q=="], + + "terser-webpack-plugin": ["terser-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "to-fast-properties": ["to-fast-properties@2.0.0", "", {}, "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="], + + "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], + + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + + "webpack": ["webpack@5.105.0", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw=="], + + "webpack-sources": ["webpack-sources@3.5.0", "", {}, "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ=="], + + "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "css-loader/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + } +} diff --git a/artifacts/glm52-rise-video/out/flash-share.mp4 b/artifacts/glm52-rise-video/out/flash-share.mp4 new file mode 100644 index 000000000000..3e182b4c1126 Binary files /dev/null and b/artifacts/glm52-rise-video/out/flash-share.mp4 differ diff --git a/artifacts/glm52-rise-video/out/glm-52-broke-out.mp4 b/artifacts/glm52-rise-video/out/glm-52-broke-out.mp4 new file mode 100644 index 000000000000..eb456d034540 Binary files /dev/null and b/artifacts/glm52-rise-video/out/glm-52-broke-out.mp4 differ diff --git a/artifacts/glm52-rise-video/out/june-totals.png b/artifacts/glm52-rise-video/out/june-totals.png new file mode 100644 index 000000000000..678376dbed69 Binary files /dev/null and b/artifacts/glm52-rise-video/out/june-totals.png differ diff --git a/artifacts/glm52-rise-video/out/minimax-climb.mp4 b/artifacts/glm52-rise-video/out/minimax-climb.mp4 new file mode 100644 index 000000000000..8ca6b678ae8c Binary files /dev/null and b/artifacts/glm52-rise-video/out/minimax-climb.mp4 differ diff --git a/artifacts/glm52-rise-video/out/novel-1984.mp4 b/artifacts/glm52-rise-video/out/novel-1984.mp4 new file mode 100644 index 000000000000..b94a8992578e Binary files /dev/null and b/artifacts/glm52-rise-video/out/novel-1984.mp4 differ diff --git a/artifacts/glm52-rise-video/out/nz-sheep.mp4 b/artifacts/glm52-rise-video/out/nz-sheep.mp4 new file mode 100644 index 000000000000..64d382da2659 Binary files /dev/null and b/artifacts/glm52-rise-video/out/nz-sheep.mp4 differ diff --git a/artifacts/glm52-rise-video/package.json b/artifacts/glm52-rise-video/package.json new file mode 100644 index 000000000000..fb08574eb222 --- /dev/null +++ b/artifacts/glm52-rise-video/package.json @@ -0,0 +1,24 @@ +{ + "name": "glm52-rise-video", + "private": true, + "type": "module", + "scripts": { + "render": "remotion render src/index.tsx GLM52Rise out/glm-52-broke-out.mp4 --codec h264 --pixel-format yuv420p", + "render:sheep": "remotion render src/index.tsx NZSheep out/nz-sheep.mp4 --codec h264 --pixel-format yuv420p", + "render:novel": "remotion render src/index.tsx NovelTokens out/novel-1984.mp4 --codec h264 --pixel-format yuv420p", + "render:flash": "remotion render src/index.tsx FlashShare out/flash-share.mp4 --codec h264 --pixel-format yuv420p", + "render:minimax": "remotion render src/index.tsx MiniMaxClimb out/minimax-climb.mp4 --codec h264 --pixel-format yuv420p", + "still:june": "remotion still src/index.tsx JuneTotals out/june-totals.png --frame=0" + }, + "dependencies": { + "@remotion/cli": "^4.0.384", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "remotion": "^4.0.384" + }, + "devDependencies": { + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "typescript": "^5.8.2" + } +} diff --git a/artifacts/glm52-rise-video/public/book.jpg b/artifacts/glm52-rise-video/public/book.jpg new file mode 100644 index 000000000000..662ca244cdaf Binary files /dev/null and b/artifacts/glm52-rise-video/public/book.jpg differ diff --git a/artifacts/glm52-rise-video/public/sheep.jpg b/artifacts/glm52-rise-video/public/sheep.jpg new file mode 100644 index 000000000000..d9ed232a89ae Binary files /dev/null and b/artifacts/glm52-rise-video/public/sheep.jpg differ diff --git a/artifacts/glm52-rise-video/src/data.ts b/artifacts/glm52-rise-video/src/data.ts new file mode 100644 index 000000000000..d9fab2a63114 --- /dev/null +++ b/artifacts/glm52-rise-video/src/data.ts @@ -0,0 +1,156 @@ +// Verified against the production opencode-stats PlanetScale DB. +// Scope: tier='Go' (OpenCode Go), dataset='zen', client='all', source='all', grain='day'. +// metric = total_tokens. Daily token volume per model; GLM-5.2 (zhipu) launched Jun 17. +// Segments per day (tokens): glm-5.2, deepseek-v4-flash, deepseek-v4-pro, minimax-m3, all others. + +export type Day = { + date: string + total: number + glm: number + dsf: number + dsp: number + mm: number + others: number +} + +export const days: Day[] = [ + { + date: "Jun 12", + total: 2_283_799_449_383, + glm: 0, + dsf: 1_176_701_653_509, + dsp: 569_527_034_307, + mm: 159_016_250_684, + others: 378_554_510_883, + }, + { + date: "Jun 13", + total: 2_008_462_388_420, + glm: 0, + dsf: 995_338_131_997, + dsp: 445_817_536_548, + mm: 211_743_241_967, + others: 355_563_477_908, + }, + { + date: "Jun 14", + total: 2_007_785_405_251, + glm: 0, + dsf: 983_954_176_228, + dsp: 428_151_999_341, + mm: 262_476_527_930, + others: 333_202_701_752, + }, + { + date: "Jun 15", + total: 2_694_736_103_062, + glm: 0, + dsf: 1_255_893_953_859, + dsp: 632_223_338_376, + mm: 352_507_442_991, + others: 454_111_367_836, + }, + { + date: "Jun 16", + total: 2_838_153_758_908, + glm: 0, + dsf: 1_336_625_283_800, + dsp: 676_480_415_730, + mm: 305_268_829_013, + others: 519_779_230_365, + }, + { + date: "Jun 17", + total: 2_778_964_711_109, + glm: 70_095_977_043, + dsf: 1_339_831_523_555, + dsp: 660_414_395_220, + mm: 251_302_096_157, + others: 457_320_719_134, + }, + { + date: "Jun 18", + total: 2_806_992_430_656, + glm: 201_130_231_172, + dsf: 1_295_599_996_869, + dsp: 595_665_008_776, + mm: 322_205_104_324, + others: 392_392_089_515, + }, + { + date: "Jun 19", + total: 2_419_611_630_232, + glm: 199_086_413_910, + dsf: 1_115_750_468_802, + dsp: 475_965_869_304, + mm: 303_586_698_735, + others: 325_222_179_481, + }, + { + date: "Jun 20", + total: 2_188_278_916_865, + glm: 193_931_516_396, + dsf: 1_050_194_681_012, + dsp: 395_303_435_278, + mm: 281_998_000_337, + others: 266_851_283_842, + }, + { + date: "Jun 21", + total: 2_042_309_961_344, + glm: 181_894_043_118, + dsf: 985_164_570_580, + dsp: 368_194_079_542, + mm: 259_812_551_324, + others: 247_244_716_780, + }, + { + date: "Jun 22", + total: 2_893_934_325_663, + glm: 301_759_048_475, + dsf: 1_298_124_282_989, + dsp: 581_012_596_194, + mm: 371_581_117_839, + others: 341_457_280_166, + }, + { + date: "Jun 23", + total: 3_109_009_321_480, + glm: 282_277_235_158, + dsf: 1_423_571_678_821, + dsp: 627_374_654_587, + mm: 429_416_300_508, + others: 346_369_452_406, + }, + { + date: "Jun 24", + total: 2_939_149_971_595, + glm: 256_497_442_533, + dsf: 1_373_583_023_234, + dsp: 601_270_997_775, + mm: 391_586_493_231, + others: 316_212_014_822, + }, + { + date: "Jun 25", + total: 3_029_641_552_948, + glm: 256_279_657_734, + dsf: 1_481_084_002_776, + dsp: 602_077_167_287, + mm: 375_985_302_874, + others: 314_215_422_277, + }, +] + +export const launchIndex = 5 // Jun 17, first day of GLM-5.2 usage +// GLM-5.2 weekly token volume, Jun 19-25 (sum of glm): 1,671,725,357,324 = 1.67T +export const glmWeekTokensT = 1.672 + +// stacked segments, bottom -> top. GLM-5.2 is the hero (blue); the rest are the field it cut into. +export const segments = [ + { key: "glm", label: "GLM-5.2", color: "#3b5cf6", hero: true }, + { key: "dsf", label: "deepseek-v4-flash", color: "#9ca3ad" }, + { key: "dsp", label: "deepseek-v4-pro", color: "#b3b9c1" }, + { key: "mm", label: "minimax-m3", color: "#c8cdd3" }, + { key: "others", label: "other models", color: "#dde0e4" }, +] as const diff --git a/artifacts/glm52-rise-video/src/flash.tsx b/artifacts/glm52-rise-video/src/flash.tsx new file mode 100644 index 000000000000..5d7aaef9bf5e --- /dev/null +++ b/artifacts/glm52-rise-video/src/flash.tsx @@ -0,0 +1,185 @@ +import React from "react" +import { AbsoluteFill, Easing, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion" + +// stats.opencode.ai design tokens (light theme) +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#e4e4e4", + gray: "#aab0b8", + accent: "#3b5cf6", + accentHi: "#5b78ff", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: OpenCode Go, week of Jun 22-28, 2026 (2026-W26). share = % of 19.64T total Go tokens. +const bars = [ + { label: "deepseek-v4-flash", share: 48.3, hero: true }, + { label: "deepseek-v4-pro", share: 19.4 }, + { label: "minimax-m3", share: 13.0 }, + { label: "glm-5.2", share: 8.3 }, + { label: "mimo-v2.5", share: 4.3 }, + { label: "kimi-k2.7-code", share: 2.6 }, + { label: "other models", share: 4.1 }, +] + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function FlashShare() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + const grow = (i: number) => + Math.min( + 1, + Math.max(0, spring({ frame: frame - 18 - i * 7, fps, config: { damping: 18, stiffness: 120, mass: 0.6 } })), + ) + + return ( + +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* headline (static) */} +
+
+ OPENCODE GO · SHARE OF TOKENS +
+
+
DeepSeek V4 Flash
+
+ 48% +
+
+
+ + {/* bar chart */} +
+ {bars.map((b, i) => { + const g = grow(i) + const pct = b.share * g + return ( +
+
+ {b.label} +
+
+ {/* dotted 100% track — height is a multiple of the 12px tile, anchored bottom, so dots never clip */} +
+ {/* fill */} +
+
+
+ {Math.round(pct)}% +
+
+ ) + })} +
+ + {/* footer */} +
+
+ + DeepSeek V4 Flash · 9.48T tokens · 83.6M requests +
+
opencode.ai/data
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/index.tsx b/artifacts/glm52-rise-video/src/index.tsx new file mode 100644 index 000000000000..3265151ac542 --- /dev/null +++ b/artifacts/glm52-rise-video/src/index.tsx @@ -0,0 +1,36 @@ +import { Composition, registerRoot } from "remotion" +import { GLM52Rise } from "./video" +import { NZSheep } from "./sheep" +import { NovelTokens } from "./novel" +import { FlashShare } from "./flash" +import { MiniMaxClimb } from "./minimax" +import { JuneTotals } from "./june" + +function Root() { + return ( + <> + + + + + + + + ) +} + +registerRoot(Root) diff --git a/artifacts/glm52-rise-video/src/june.tsx b/artifacts/glm52-rise-video/src/june.tsx new file mode 100644 index 000000000000..aa3f9de9bef4 --- /dev/null +++ b/artifacts/glm52-rise-video/src/june.tsx @@ -0,0 +1,144 @@ +import React from "react" +import { AbsoluteFill } from "remotion" + +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#dcdcdc", + accent: "#3b5cf6", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: OpenCode Go (tier=Go, dataset=zen), June 1-30, 2026. +// 72.78T tokens · 651.4M requests · 11.42M sessions -> rounded headline figures. + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +function DotBand() { + return ( +
+ ) +} + +function Metric({ value, label }: { value: string; label: string }) { + return ( +
+
+ {value} +
+
{label}
+
+ ) +} + +export function JuneTotals() { + return ( + +
+ {/* header */} +
+ +
MONTHLY RECAP
+
+ + + {/* hero */} +
+
OPENCODE GO · JUNE 2026
+
+
+ 73T +
+
tokens processed
+
+ + {/* supporting metrics */} +
+ + +
+
+ + {/* footer */} + +
+
opencode.ai/data
+
+
+
+ ) +} diff --git a/artifacts/glm52-rise-video/src/minimax.tsx b/artifacts/glm52-rise-video/src/minimax.tsx new file mode 100644 index 000000000000..44cf09a1e75a --- /dev/null +++ b/artifacts/glm52-rise-video/src/minimax.tsx @@ -0,0 +1,201 @@ +import React from "react" +import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion" + +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#e4e4e4", + accent: "#3b5cf6", + accentHi: "#5b78ff", + accentDim: "#aebcf3", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: minimax-m3, OpenCode Go (tier=Go, dataset=zen), weekly total_tokens. +// W26 2.559T is +23.2% / +482.3B vs W25 2.077T. +const weeks = [ + { label: "May 25", t: 0.008 }, + { label: "Jun 1", t: 0.429 }, + { label: "Jun 8", t: 1.192 }, + { label: "Jun 15", t: 2.077 }, + { label: "Jun 22", t: 2.559, latest: true }, +] +const AXIS_MAX = 3.0 + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +const CHART_H = 420 // multiple of 12 so the dotted tracks never clip + +export function MiniMaxClimb() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + const grow = (i: number) => + Math.min( + 1, + Math.max(0, spring({ frame: frame - 22 - i * 9, fps, config: { damping: 18, stiffness: 110, mass: 0.6 } })), + ) + + return ( + +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* headline (static) */} +
+
+ OPENCODE GO · WEEKLY TOKENS +
+
+
MiniMax M3
+
+
+ +23.2% +
+
+ WEEK OVER WEEK +
+
+
+
+ + {/* column chart */} +
+
+ {weeks.map((w, i) => { + const g = grow(i) + const h = Math.round((w.t / AXIS_MAX) * CHART_H * g) + return ( +
+ {/* value + bar */} +
+ {/* dotted track */} +
+ {/* fill */} +
+ {/* value label */} +
+ {w.t.toFixed(2)}T +
+
+ {/* week label */} +
+ {w.label} +
+
+ ) + })} +
+
+ + {/* footer */} +
+
+ + 2.56T tokens last week · +482.3B added +
+
opencode.ai/data
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/novel.tsx b/artifacts/glm52-rise-video/src/novel.tsx new file mode 100644 index 000000000000..9d649de4bbbb --- /dev/null +++ b/artifacts/glm52-rise-video/src/novel.tsx @@ -0,0 +1,135 @@ +import React from "react" +import { AbsoluteFill, Easing, Img, interpolate, staticFile, useCurrentFrame } from "remotion" + +const c = { + white: "#ffffff", + dim: "rgba(255,255,255,0.74)", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +// verified: OpenCode Go, week of Jun 22-28, 2026 (2026-W26) +// 19,642,742,937,105 tokens / 173,651,197 requests = 113,116 tokens/request +const AVG = 113116 +const K = Math.round(AVG / 1000) // 113 + +const nf = new Intl.NumberFormat("en-US") + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function NovelTokens() { + const frame = useCurrentFrame() + + const k = Math.round( + K * + interpolate(frame, [18, 92], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }), + ) + + const zoom = interpolate(frame, [0, 150], [1.06, 1.12], { + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.quad), + }) + + return ( + + + + {/* legibility scrims */} +
+ +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* bottom block */} +
+
+ OPENCODE GO · LAST WEEK +
+
+ {k}K +
+
tokens per request
+ +
+
{nf.format(AVG)} tokens / request · last week
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/sheep.tsx b/artifacts/glm52-rise-video/src/sheep.tsx new file mode 100644 index 000000000000..53abb39ee567 --- /dev/null +++ b/artifacts/glm52-rise-video/src/sheep.tsx @@ -0,0 +1,139 @@ +import React from "react" +import { AbsoluteFill, Easing, Img, interpolate, staticFile, useCurrentFrame } from "remotion" + +const c = { + white: "#ffffff", + dim: "rgba(255,255,255,0.72)", + faint: "rgba(255,255,255,0.55)", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +// verified: NZ OpenCode Go, week of Jun 22-28, 2026 (2026-W26) +const TOKENS = 40_915_594_381 // 40.9B +const SHEEP = 23_600_000 // 23.6M +const PER_SHEEP = Math.round(TOKENS / SHEEP) // 1,734 +const nf = new Intl.NumberFormat("en-US") + +// the correct opencode "DATA" wordmark (white, over photo) +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function NZSheep() { + const frame = useCurrentFrame() + + const count = Math.round( + PER_SHEEP * + interpolate(frame, [18, 90], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }), + ) + + // slow Ken Burns push-in (scale up only — never reveals an edge) + const zoom = interpolate(frame, [0, 150], [1.06, 1.12], { + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.quad), + }) + + return ( + + {/* the sheep, staring */} + + + {/* legibility scrims */} +
+ + {/* content */} +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* bottom block */} +
+
+ OPENCODE GO · NEW ZEALAND +
+
+ {nf.format(count)} +
+
tokens per sheep
+ +
+
40.9B tokens ÷ 23.6M sheep · last week
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/video.tsx b/artifacts/glm52-rise-video/src/video.tsx new file mode 100644 index 000000000000..41466a7e6c39 --- /dev/null +++ b/artifacts/glm52-rise-video/src/video.tsx @@ -0,0 +1,254 @@ +import React from "react" +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion" +import { days, launchIndex, glmWeekTokensT, segments } from "./data" + +// stats.opencode.ai design tokens (light theme) +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#ededed", + accent: "#3b5cf6", + accentHi: "#5b78ff", +} + +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' +const W = 1080 + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +const field = segments.filter((s) => !s.hero) +const glmColor = segments.find((s) => s.hero)!.color + +const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v)) + +// the correct opencode "DATA" wordmark (from stats.opencode.ai header) +function DataWordmark({ height = 30, color = c.ink }: { height?: number; color?: string }) { + return ( + + + + + + + + + ) +} + +export function GLM52Rise() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + // ---------- virtual camera ---------- + // open zoomed-in on the field, pan right while the blue fills, then pull back to reveal. + const K = [0, 32, 150, 206, 240] + const ease = Easing.inOut(Easing.cubic) + const opt = { extrapolateLeft: "clamp" as const, extrapolateRight: "clamp" as const, easing: ease } + const s = interpolate(frame, K, [1.82, 1.72, 1.72, 1.0, 1.0], opt) + let fx = interpolate(frame, K, [420, 438, 760, 540, 540], opt) + let fy = interpolate(frame, K, [664, 664, 664, 540, 540], opt) + // keep the framing inside the 1080 canvas so edges never reveal black + fx = clamp(fx, 540 / s, W - 540 / s) + fy = clamp(fy, 540 / s, W - 540 / s) + const camera = `translate(${540 - fx * s}px, ${540 - fy * s}px) scale(${s})` + + // ---------- blue sweep (synced to the pan) ---------- + const p = interpolate(frame, [32, 150], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: ease }) + const revealAmount = p * (days.length - launchIndex) + 0.35 + const fillOf = (i: number) => clamp(revealAmount - (i - launchIndex), 0, 1) + + // token number climbs as the camera pulls back and the headline re-enters frame + const tokensT = + glmWeekTokensT * + interpolate(frame, [150, 202], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }) + + // chart geometry + const chartH = 440 + const chartW = 936 + const gap = 14 + const EXAGGERATE = 2.876 // broken y-axis: GLM-5.2 magnified ~2.9x for emphasis + + return ( + +
+
+ {/* header */} +
+ +
JUN 12–25, 2026
+
+ + {/* headline (static) */} +
+
+ GLM-5.2 broke out +
+
+ From 0 to {tokensT.toFixed(2)}T tokens in a week. +
+
+ + {/* stacked chart */} +
+
+ {/* faint dotted backdrop */} +
+ + {/* columns */} +
+ {days.map((d, i) => { + const glmShare = d.glm / d.total + const blueH = Math.round(glmShare * EXAGGERATE * chartH) + const filled = Math.round(blueH * fillOf(i)) + const slotGap = filled > 2 ? 5 : 0 + const fieldH = chartH - filled - slotGap + const fieldTotal = d.dsf + d.dsp + d.mm + d.others + return ( +
+ {/* gray field of other models (already in place) */} +
+ {field.map((seg) => ( +
+ ))} +
+ {/* GLM-5.2, animates in */} + {filled > 2 && ( +
+ )} +
+ ) + })} +
+
+
+ + {/* day axis */} +
+ {days.map((d, i) => ( +
+ {i === 0 || i === launchIndex || i === days.length - 1 ? d.date : ""} +
+ ))} +
+ + {/* footer */} +
+
+ + GLM-5.2 +
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/sst-env.d.ts b/artifacts/glm52-rise-video/sst-env.d.ts new file mode 100644 index 000000000000..64441936d7a0 --- /dev/null +++ b/artifacts/glm52-rise-video/sst-env.d.ts @@ -0,0 +1,10 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +/* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ + +/// + +import "sst" +export {} \ No newline at end of file diff --git a/bun.lock b/bun.lock index c7ac6ab61c77..4eb9d477edc8 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,7 @@ }, "devDependencies": { "@actions/artifact": "5.0.1", + "@ast-grep/cli": "0.44.0", "@tsconfig/bun": "catalog:", "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", @@ -24,19 +25,21 @@ "prettier": "3.6.2", "semver": "^7.6.0", "sst": "catalog:", - "turbo": "2.8.13", + "turbo": "2.10.2", }, }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { + "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", "@dnd-kit/helpers": "0.5.0", "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -71,6 +74,7 @@ "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", + "solid-presence": "0.2.0", "tailwindcss": "catalog:", }, "devDependencies": { @@ -83,6 +87,7 @@ "@types/luxon": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", + "tw-animate-css": "1.4.0", "typescript": "catalog:", "vite": "catalog:", "vite-plugin-icons-spritesheet": "3.0.1", @@ -91,40 +96,52 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.17.11", + "version": "1.17.18", "bin": { - "lildax": "./bin/lildax.cjs", + "opencode2": "./bin/opencode2.cjs", }, "dependencies": { "@effect/platform-node": "catalog:", + "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", + "@opencode-ai/plugin": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/server": "workspace:*", "@opencode-ai/tui": "workspace:*", "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", "effect": "catalog:", + "fuzzysort": "catalog:", + "immer": "11.1.4", + "jsonc-parser": "3.3.1", + "open": "10.1.2", + "opentui-spinner": "catalog:", + "semver": "catalog:", "solid-js": "catalog:", + "strip-ansi": "7.1.2", + "uqr": "0.1.3", }, "devDependencies": { "@opencode-ai/script": "workspace:*", "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", + "@types/semver": "catalog:", "@typescript/native-preview": "catalog:", }, }, "packages/client": { "name": "@opencode-ai/client", + "version": "1.17.13", "dependencies": { "@opencode-ai/protocol": "workspace:*", "@opencode-ai/schema": "workspace:*", }, "devDependencies": { "@effect/platform-node": "catalog:", - "@opencode-ai/core": "workspace:*", "@opencode-ai/httpapi-codegen": "workspace:*", - "@opencode-ai/server": "workspace:*", "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@typescript/native-preview": "catalog:", @@ -137,9 +154,23 @@ "effect", ], }, + "packages/codemode": { + "name": "@opencode-ai/codemode", + "version": "1.17.18", + "dependencies": { + "acorn": "8.15.0", + "effect": "catalog:", + "typescript": "catalog:", + }, + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + }, + }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -175,7 +206,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -202,7 +233,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -224,7 +255,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -248,7 +279,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -268,7 +299,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.17.11", + "version": "1.17.18", "bin": { "opencode": "./bin/opencode", }, @@ -292,15 +323,17 @@ "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", "@ff-labs/fff-bun": "0.9.4", "@lydell/node-pty": "catalog:", + "@modelcontextprotocol/sdk": "1.29.0", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", + "@opencode-ai/codemode": "workspace:*", "@opencode-ai/effect-drizzle-sqlite": "workspace:*", "@opencode-ai/effect-sqlite-node": "workspace:*", "@opencode-ai/llm": "workspace:*", @@ -316,10 +349,11 @@ "ai-gateway-provider": "3.1.2", "bun-pty": "0.4.8", "cross-spawn": "catalog:", + "diff": "catalog:", "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.9.3", + "gitlab-ai-provider": "6.10.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -332,7 +366,7 @@ "npm-package-arg": "13.0.2", "semver": "^7.6.3", "turndown": "7.2.0", - "venice-ai-sdk-provider": "2.0.2", + "venice-ai-sdk-provider": "2.1.1", "which": "6.0.1", "xdg-basedir": "5.1.0", "zod": "catalog:", @@ -361,7 +395,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@zip.js/zip.js": "2.7.62", "effect": "catalog:", @@ -413,9 +447,15 @@ "@parcel/watcher-win32-x64": "2.5.1", }, }, + "packages/docs": { + "name": "@opencode-ai/docs", + "devDependencies": { + "mint": "4.2.666", + }, + }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -429,7 +469,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "effect": "catalog:", }, @@ -441,7 +481,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -473,7 +513,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -489,12 +529,12 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { - "@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83", }, "devDependencies": { + "@effect/platform-node": "catalog:", "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", @@ -503,11 +543,12 @@ "typescript": "catalog:", }, "peerDependencies": { - "effect": "4.0.0-beta.83", + "effect": "catalog:", }, }, "packages/httpapi-codegen": { "name": "@opencode-ai/httpapi-codegen", + "version": "0.0.0", "dependencies": { "effect": "catalog:", "prettier": "3.6.2", @@ -520,7 +561,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@opencode-ai/schema": "workspace:*", "@smithy/eventstream-codec": "4.2.14", @@ -539,7 +580,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.17.11", + "version": "1.17.18", "bin": { "opencode": "./bin/opencode", }, @@ -551,7 +592,7 @@ "@ai-sdk/amazon-bedrock": "4.0.112", "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.49", - "@ai-sdk/cerebras": "2.0.41", + "@ai-sdk/cerebras": "2.0.60", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", @@ -565,7 +606,7 @@ "@ai-sdk/provider": "3.0.8", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", @@ -576,6 +617,9 @@ "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", + "@opencode-ai/cli": "workspace:*", + "@opencode-ai/client": "workspace:*", + "@opencode-ai/codemode": "workspace:*", "@opencode-ai/llm": "workspace:*", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/protocol": "workspace:*", @@ -611,7 +655,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.9.3", + "gitlab-ai-provider": "6.10.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -635,7 +679,7 @@ "tree-sitter-powershell": "0.25.10", "turndown": "7.2.0", "ulid": "catalog:", - "venice-ai-sdk-provider": "2.0.2", + "venice-ai-sdk-provider": "2.1.1", "vscode-jsonrpc": "8.2.1", "web-tree-sitter": "0.25.10", "ws": "8.21.0", @@ -669,9 +713,11 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@ai-sdk/provider": "3.0.8", + "@opencode-ai/client": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", "zod": "catalog:", @@ -680,15 +726,16 @@ "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", + "@tsconfig/bun": "catalog:", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.3.4", - "@opentui/keymap": ">=0.3.4", - "@opentui/solid": ">=0.3.4", + "@opentui/core": ">=0.4.3", + "@opentui/keymap": ">=0.4.3", + "@opentui/solid": ">=0.4.3", }, "optionalPeers": [ "@opentui/core", @@ -698,6 +745,7 @@ }, "packages/protocol": { "name": "@opencode-ai/protocol", + "version": "1.17.11", "dependencies": { "@opencode-ai/schema": "workspace:*", "effect": "catalog:", @@ -706,10 +754,12 @@ "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@typescript/native-preview": "catalog:", + "typescript": "catalog:", }, }, "packages/schema": { "name": "@opencode-ai/schema", + "version": "1.17.11", "dependencies": { "effect": "catalog:", }, @@ -717,6 +767,7 @@ "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@typescript/native-preview": "catalog:", + "typescript": "catalog:", }, }, "packages/script": { @@ -734,10 +785,14 @@ "dependencies": { "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", + "@opencode-ai/plugin": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/server": "workspace:*", "effect": "catalog:", }, "devDependencies": { + "@opencode-ai/httpapi-codegen": "workspace:*", + "@opencode-ai/protocol": "workspace:*", "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@typescript/native-preview": "catalog:", @@ -745,7 +800,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "cross-spawn": "catalog:", }, @@ -760,10 +815,12 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { + "@effect/platform-node": "catalog:", "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", + "@opencode-ai/simulation": "workspace:*", "drizzle-orm": "catalog:", "effect": "catalog:", }, @@ -775,7 +832,7 @@ }, "packages/session-ui": { "name": "@opencode-ai/session-ui", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -817,9 +874,26 @@ "vite": "catalog:", }, }, + "packages/simulation": { + "name": "@opencode-ai/simulation", + "version": "1.17.13", + "dependencies": { + "@fontsource/adwaita-mono": "5.2.1", + "@napi-rs/canvas": "1.0.2", + "@opencode-ai/core": "workspace:*", + "@opencode-ai/llm": "workspace:*", + "@opentui/core": "catalog:", + "effect": "catalog:", + }, + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + }, + }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -832,7 +906,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@ibm/plex": "6.4.1", "@opencode-ai/stats-core": "workspace:*", @@ -865,7 +939,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -884,7 +958,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -925,15 +999,18 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { + "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/simulation": "workspace:*", "@opencode-ai/ui": "workspace:*", "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", + "@solid-primitives/event-bus": "1.1.2", "clipboardy": "4.0.0", "diff": "catalog:", "effect": "catalog:", @@ -943,6 +1020,7 @@ "remeda": "catalog:", "solid-js": "catalog:", "strip-ansi": "7.1.2", + "uqr": "0.1.3", }, "devDependencies": { "@tsconfig/bun": "catalog:", @@ -952,7 +1030,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@kobalte/core": "catalog:", "@pierre/diffs": "catalog:", @@ -990,6 +1068,7 @@ "@typescript/native-preview": "catalog:", "solid-js": "catalog:", "tailwindcss": "catalog:", + "tw-animate-css": "1.4.0", "typescript": "catalog:", "vite": "catalog:", "vite-plugin-icons-spritesheet": "3.0.1", @@ -1002,7 +1081,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.17.11", + "version": "1.17.18", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -1045,15 +1124,16 @@ ], "patchedDependencies": { "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", + "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", }, @@ -1066,6 +1146,7 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", + "@corvu/drawer": "0.2.4", "@effect/opentelemetry": "4.0.0-beta.83", "@effect/platform-node": "4.0.0-beta.83", "@effect/sql-sqlite-bun": "4.0.0-beta.83", @@ -1076,9 +1157,9 @@ "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.3.4", - "@opentui/keymap": "0.3.4", - "@opentui/solid": "0.3.4", + "@opentui/core": "0.4.3", + "@opentui/keymap": "0.4.3", + "@opentui/solid": "0.4.3", "@pierre/diffs": "1.2.10", "@playwright/test": "1.59.1", "@sentry/solid": "10.36.0", @@ -1191,7 +1272,9 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.102", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NeQyOR7OCqDMgaLS4uNX/ep/HrwUzzFYLzXQSRoqLy2jsnqxAJhsgltRwAwf+ADjyPBIAKEOestWnIQA+LrLrQ=="], + + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -1201,6 +1284,26 @@ "@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="], + "@ark/schema": ["@ark/schema@0.55.0", "", { "dependencies": { "@ark/util": "0.55.0" } }, "sha512-IlSIc0FmLKTDGr4I/FzNHauMn0MADA6bCjT1wauu4k6MyxhC1R9gz0olNpIRvK7lGGDwtc/VO0RUDNvVQW5WFg=="], + + "@ark/util": ["@ark/util@0.55.0", "", {}, "sha512-aWFNK7aqSvqFtVsl1xmbTjGbg91uqtJV7Za76YGNEwIO4qLjMfyY8flmmbhooYMuqPCO2jyxu8hve943D+w3bA=="], + + "@ast-grep/cli": ["@ast-grep/cli@0.44.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@ast-grep/cli-darwin-arm64": "0.44.0", "@ast-grep/cli-darwin-x64": "0.44.0", "@ast-grep/cli-linux-arm64-gnu": "0.44.0", "@ast-grep/cli-linux-x64-gnu": "0.44.0", "@ast-grep/cli-win32-arm64-msvc": "0.44.0", "@ast-grep/cli-win32-ia32-msvc": "0.44.0", "@ast-grep/cli-win32-x64-msvc": "0.44.0" }, "bin": { "sg": "sg", "ast-grep": "ast-grep" } }, "sha512-Jf4PuP7XjzsMa3m9gYxmzV8KyWZc4w1ZzKe/t0+90wWxmSasQJe6AtMkJxHEi98MGgfAF1nWziqjDd0/6EsBjA=="], + + "@ast-grep/cli-darwin-arm64": ["@ast-grep/cli-darwin-arm64@0.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bF7euu/hF/cYg4510z8110vh60rrqfrBdsfRqVGd6xqNSPENu7CJnTVN/Z4Nk5U1NM8YKzUD+dYx1ySUJ0CUNQ=="], + + "@ast-grep/cli-darwin-x64": ["@ast-grep/cli-darwin-x64@0.44.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0fI9caQGp1dFcmBATNlVytIRdAeYb91v1D2xjMIi1bSX+l8Uj846JUiaimUGBuBZmyFq+BScoWM4RnprEmZMpQ=="], + + "@ast-grep/cli-linux-arm64-gnu": ["@ast-grep/cli-linux-arm64-gnu@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JB6EUnqEtGGtyg1GqNquld/++1CvaWD7r84IwwhddX1qx0NmDoHyn2mKd8vnQ24Z0RkV3g7y7foMLakELbGtDw=="], + + "@ast-grep/cli-linux-x64-gnu": ["@ast-grep/cli-linux-x64-gnu@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rNL0LsI682D9EMzfaGVEtZa1xaqTtGb2I+Zk4ZzidX6u+fF7f79wdqyKahKjXzoIrGkuhkoL3gcyLKAtQd9+qg=="], + + "@ast-grep/cli-win32-arm64-msvc": ["@ast-grep/cli-win32-arm64-msvc@0.44.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-lqD0MhGQAddh2YoV/brKQ6GVcFLmRiTBwIElutwedaUvRCdasTGukFPYuSWk/iI8Kv19xom6s7l+mGuZ7v+xwQ=="], + + "@ast-grep/cli-win32-ia32-msvc": ["@ast-grep/cli-win32-ia32-msvc@0.44.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZJrnS+2OkNfwyr6yrN69glP67uybBxDvl9mqZvh1J44vB3OFn9U9c+cVAoZIAo7JD5F4rZNxwyu3gcy4+xuwEA=="], + + "@ast-grep/cli-win32-x64-msvc": ["@ast-grep/cli-win32-x64-msvc@0.44.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OJEo7f95YYaSuS1byUB7ZctbzxoA7/wCoAol+pt6pvfdW/8Wq+L1qU28glwx7dQ0HgTsnPZbWpXQwmZpCBHhZg=="], + "@astrojs/check": ["@astrojs/check@0.9.6", "", { "dependencies": { "@astrojs/language-server": "^2.16.1", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA=="], "@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="], @@ -1229,6 +1332,10 @@ "@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="], + "@asyncapi/parser": ["@asyncapi/parser@3.4.0", "", { "dependencies": { "@asyncapi/specs": "^6.8.0", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json": "3.21.0", "@stoplight/json-ref-readers": "^1.2.2", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.18.3", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-ref-resolver": "^1.0.3", "@stoplight/types": "^13.12.0", "@types/json-schema": "^7.0.11", "@types/urijs": "^1.19.19", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "avsc": "^5.7.5", "js-yaml": "^4.1.0", "jsonpath-plus": "^10.0.0", "node-fetch": "2.6.7" } }, "sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ=="], + + "@asyncapi/specs": ["@asyncapi/specs@6.8.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA=="], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], @@ -1423,6 +1530,8 @@ "@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.12.0", "", { "dependencies": { "@bufbuild/protobuf": "2.12.0", "@typescript/vfs": "^1.6.2", "typescript": "5.4.5" } }, "sha512-ORlDITp8AFUXzIhLRoMCG+ud+D3MPKWb5HQXBoskMMnjeyEjE1H1qLonVNPyOr8lkx3xSfYUo8a0dvOZJVAzow=="], + "@canvas/image-data": ["@canvas/image-data@1.1.0", "", {}, "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA=="], + "@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="], "@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="], @@ -1447,6 +1556,10 @@ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="], + "@corvu/dialog": ["@corvu/dialog@0.2.4", "", { "dependencies": { "@corvu/utils": "~0.4.2", "solid-dismissible": "~0.1.1", "solid-focus-trap": "~0.1.8", "solid-presence": "~0.2.0", "solid-prevent-scroll": "~0.1.10" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA=="], + + "@corvu/drawer": ["@corvu/drawer@0.2.4", "", { "dependencies": { "@corvu/dialog": "~0.2.4", "@corvu/utils": "~0.4.2", "@solid-primitives/memo": "^1.4.1", "solid-transition-size": "~0.1.4" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-7jQoGZ8ROB9CmXam2nMY2wEskU3IoFwZQywkF/7vrBc/edGsPv7mOVQ1GN6G+4nd7nrMZ3UtqHAhmRh9V0azlw=="], + "@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], @@ -1511,11 +1624,11 @@ "@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], - "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="], @@ -1623,6 +1736,8 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + "@fontsource/adwaita-mono": ["@fontsource/adwaita-mono@5.2.1", "", {}, "sha512-6+Q1UIvklJ9REijs6kv7YlRNt6yktRj0iW8H69YIugdD9P2h3eIX1AB8/9ICMfpVyVeywlsrCXg82y/LfRrjyg=="], + "@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.2.5", "", {}, "sha512-G09N3GfuT9qj3Ax2FDZvKqZttzM3v+cco2l8uXamhKyXLdmlaUDH5o88/C3vtTHj2oT7yRKsvxz9F+BXbWKMYA=="], "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], @@ -1689,6 +1804,38 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], + + "@inquirer/checkbox": ["@inquirer/checkbox@4.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA=="], + + "@inquirer/confirm": ["@inquirer/confirm@5.1.21", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="], + + "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="], + + "@inquirer/editor": ["@inquirer/editor@4.2.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/external-editor": "^1.0.3", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ=="], + + "@inquirer/expand": ["@inquirer/expand@4.0.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew=="], + + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], + + "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], + + "@inquirer/input": ["@inquirer/input@4.3.1", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g=="], + + "@inquirer/number": ["@inquirer/number@3.0.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg=="], + + "@inquirer/password": ["@inquirer/password@4.0.23", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA=="], + + "@inquirer/prompts": ["@inquirer/prompts@7.9.0", "", { "dependencies": { "@inquirer/checkbox": "^4.3.0", "@inquirer/confirm": "^5.1.19", "@inquirer/editor": "^4.2.21", "@inquirer/expand": "^4.0.21", "@inquirer/input": "^4.2.5", "@inquirer/number": "^3.0.21", "@inquirer/password": "^4.0.21", "@inquirer/rawlist": "^4.1.9", "@inquirer/search": "^3.2.0", "@inquirer/select": "^4.4.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A=="], + + "@inquirer/rawlist": ["@inquirer/rawlist@4.1.11", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw=="], + + "@inquirer/search": ["@inquirer/search@3.2.2", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA=="], + + "@inquirer/select": ["@inquirer/select@4.4.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w=="], + + "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], + "@internationalized/date": ["@internationalized/date@3.12.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw=="], "@internationalized/number": ["@internationalized/number@3.6.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg=="], @@ -1723,6 +1870,12 @@ "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], + "@jsep-plugin/assignment": ["@jsep-plugin/assignment@1.3.0", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ=="], + + "@jsep-plugin/regex": ["@jsep-plugin/regex@1.0.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg=="], + + "@jsep-plugin/ternary": ["@jsep-plugin/ternary@1.1.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg=="], + "@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="], "@jsx-email/body": ["@jsx-email/body@1.0.2", "", { "peerDependencies": { "react": "^18.2.0" } }, "sha512-NjR2tgLH4XGfGkm+O8kcVwi9MBqZsXZCLlmk3HlMux3/n/+a5zB+yhJqXWZBJl2i+6cSF+E2O6hK11ekyK9WWQ=="], @@ -1797,6 +1950,26 @@ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + "@mintlify/cli": ["@mintlify/cli@4.0.1269", "", { "dependencies": { "@inquirer/prompts": "7.9.0", "@mintlify/common": "1.0.985", "@mintlify/link-rot": "3.0.1172", "@mintlify/models": "0.0.333", "@mintlify/prebuild": "1.0.1131", "@mintlify/previewing": "4.0.1197", "@mintlify/validation": "0.1.769", "adm-zip": "0.5.16", "chalk": "5.2.0", "color": "4.2.3", "detect-port": "1.5.1", "front-matter": "4.0.2", "fs-extra": "11.2.0", "ink": "6.3.0", "inquirer": "12.3.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.2.0", "open": "8.4.2", "openid-client": "6.8.2", "posthog-node": "5.17.2", "react": "19.2.3", "semver": "7.7.2", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "4.3.6" }, "optionalDependencies": { "keytar": "7.9.0" }, "bin": { "mint": "bin/index.js", "mintlify": "bin/index.js" } }, "sha512-l9b7InT55JWXV7TU7Jr4Wrijv4/gMFHLQyWJ7fcjqpSxAetR+xNyeEARRlcf7OicGTjZuvmRGGfj75kp3O4p7A=="], + + "@mintlify/common": ["@mintlify/common@1.0.985", "", { "dependencies": { "@asyncapi/parser": "3.4.0", "@asyncapi/specs": "6.8.1", "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.333", "@mintlify/openapi-parser": "0.0.8", "@mintlify/validation": "0.1.769", "@sindresorhus/slugify": "2.2.0", "@types/mdast": "4.0.4", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "color-blend": "4.0.0", "estree-util-to-js": "2.0.0", "estree-walker": "3.0.3", "front-matter": "4.0.2", "hast-util-from-html": "2.0.3", "hast-util-to-html": "9.0.4", "hast-util-to-text": "4.0.2", "hex-rgb": "5.0.0", "ignore": "7.0.5", "js-yaml": "4.1.1", "lodash": "4.18.1", "mdast-util-from-markdown": "2.0.2", "mdast-util-gfm": "3.0.0", "mdast-util-mdx": "3.0.0", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-gfm": "3.0.0", "micromark-extension-mdx-jsx": "3.0.1", "micromark-extension-mdxjs": "3.0.0", "openapi-types": "12.1.3", "postcss": "8.5.14", "rehype-stringify": "10.0.1", "remark": "15.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.0", "remark-math": "6.0.0", "remark-mdx": "3.1.0", "remark-parse": "11.0.0", "remark-rehype": "11.1.1", "remark-stringify": "11.0.0", "sucrase": "3.34.0", "tailwindcss": "3.4.17", "unified": "11.0.5", "unist-builder": "4.0.0", "unist-util-map": "4.0.0", "unist-util-remove": "4.0.0", "unist-util-remove-position": "5.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1", "vfile": "6.0.3", "xss": "1.0.15" } }, "sha512-eJPeR99AKgVifXLdiA2hhfNy2+CmZ3zqQAscRXmFbJFST8SgsUj6rU3D2fx0XYt38b7T3LqEMD7DH5ipSC+Zfg=="], + + "@mintlify/link-rot": ["@mintlify/link-rot@3.0.1172", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/models": "0.0.333", "@mintlify/prebuild": "1.0.1131", "@mintlify/previewing": "4.0.1197", "@mintlify/scraping": "4.0.849", "@mintlify/validation": "0.1.769", "fs-extra": "11.1.0", "unist-util-visit": "4.1.2" } }, "sha512-8962sk/WO/0YcSkHTiaVZ2DvrCb8TC4BPgp21a9qW6nsPKKNixMhsC2uUB90D+gOzPTejJl0NNd4gLk4fA3SKw=="], + + "@mintlify/mdx": ["@mintlify/mdx@3.0.4", "", { "dependencies": { "@shikijs/transformers": "^3.11.0", "@shikijs/twoslash": "^3.12.2", "arktype": "^2.1.26", "hast-util-to-string": "^3.0.1", "mdast-util-from-markdown": "^2.0.2", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "next-mdx-remote-client": "^1.0.3", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-smartypants": "^3.0.2", "shiki": "^3.11.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@radix-ui/react-popover": "^1.1.15", "react": "^18.3.1", "react-dom": "^18.3.1" } }, "sha512-tJhdpnM5ReJLNJ2fuDRIEr0zgVd6id7/oAIfs26V46QlygiLsc8qx4Rz3LWIX51rUXW/cfakjj0EATxIciIw+g=="], + + "@mintlify/models": ["@mintlify/models@0.0.333", "", { "dependencies": { "axios": "1.16.1", "openapi-types": "12.1.3" } }, "sha512-0uAsuTsV8gYCDpv4aA0MWilQu+a/mrK+G6q5FVcgunbEZ3meeCXfrQFTviaEw7A+0cR/7Pc2KLA66cPDm+3Qdg=="], + + "@mintlify/openapi-parser": ["@mintlify/openapi-parser@0.0.8", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-9MBRq9lS4l4HITYCrqCL7T61MOb20q9IdU7HWhqYMNMM1jGO1nHjXasFy61yZ8V6gMZyyKQARGVoZ0ZrYN48Og=="], + + "@mintlify/prebuild": ["@mintlify/prebuild@1.0.1131", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/openapi-parser": "0.0.8", "@mintlify/scraping": "4.0.849", "@mintlify/validation": "0.1.769", "chalk": "5.3.0", "favicons": "7.2.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "sharp": "0.33.5", "sharp-ico": "0.1.5", "unist-util-visit": "4.1.2", "uuid": "11.1.1" } }, "sha512-EbPf1/z1m8K/Jl4qXggiMQwfdqXLF25sj+d5SHaGl6T0auTOYMayN578Rb/qM4fjhhlBQwub919Zsq9syYeYUA=="], + + "@mintlify/previewing": ["@mintlify/previewing@4.0.1197", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/prebuild": "1.0.1131", "@mintlify/validation": "0.1.769", "adm-zip": "0.5.16", "better-opn": "3.0.2", "chalk": "5.2.0", "chokidar": "3.5.3", "express": "4.22.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "got": "13.0.0", "ink": "6.3.0", "ink-spinner": "5.0.0", "is-online": "10.0.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "react": "19.2.3", "socket.io": "4.8.0", "tar": "7.5.15", "unist-util-visit": "4.1.2", "yargs": "17.7.1" } }, "sha512-q4TunK8KjE1k9ve5nOAQTvBpsE7bX+RJN/ozx7RdIyQSCSexpkywmSM6nE3ECJyjUWFy8pg6yrYYbUQLHN/oww=="], + + "@mintlify/scraping": ["@mintlify/scraping@4.0.849", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/openapi-parser": "0.0.8", "fs-extra": "11.1.1", "hast-util-to-mdast": "10.1.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.1.3", "neotraverse": "0.6.18", "puppeteer": "22.14.0", "rehype-parse": "9.0.1", "remark-gfm": "4.0.0", "remark-mdx": "3.0.1", "remark-parse": "11.0.0", "remark-stringify": "11.0.0", "unified": "11.0.5", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "3.24.0" }, "bin": { "mintlify-scrape": "bin/cli.js" } }, "sha512-4aMltLtfSU5rkUJt2SCVaYIbuggiEnx7lMWKM8NW93SaZUeoL0iKNbo0K24SmOqS7kYATRRH8zI7gefPX2ZLDw=="], + + "@mintlify/validation": ["@mintlify/validation@0.1.769", "", { "dependencies": { "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.333", "arktype": "2.1.27", "fractional-indexing": "3.2.0", "js-yaml": "4.1.1", "lcm": "0.0.3", "lodash": "4.18.1", "neotraverse": "0.6.18", "object-hash": "3.0.0", "openapi-types": "12.1.3", "uuid": "11.1.1", "zod": "3.24.0", "zod-to-json-schema": "3.20.4" } }, "sha512-8Sg6DCdQ7RSc3NIyaSSWy7G6KaAuw0NfUSBCYLwGhtp6dYPh1jEsPn6YU8hqUMNZn1OQHsA52rA6lZQxjnyLHQ=="], + "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], @@ -1825,6 +1998,30 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], + "@napi-rs/canvas": ["@napi-rs/canvas@1.0.2", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "1.0.2", "@napi-rs/canvas-darwin-arm64": "1.0.2", "@napi-rs/canvas-darwin-x64": "1.0.2", "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2", "@napi-rs/canvas-linux-arm64-gnu": "1.0.2", "@napi-rs/canvas-linux-arm64-musl": "1.0.2", "@napi-rs/canvas-linux-riscv64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-musl": "1.0.2", "@napi-rs/canvas-win32-arm64-msvc": "1.0.2", "@napi-rs/canvas-win32-x64-msvc": "1.0.2" } }, "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ=="], + + "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@1.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g=="], + + "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@1.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ=="], + + "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@1.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A=="], + + "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@1.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ=="], + + "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA=="], + + "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA=="], + + "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@1.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw=="], + + "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g=="], + + "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q=="], + + "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@1.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ=="], + + "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@1.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], @@ -1911,6 +2108,8 @@ "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], + "@openapi-contrib/openapi-schema-to-json-schema": ["@openapi-contrib/openapi-schema-to-json-schema@3.2.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw=="], + "@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], "@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"], @@ -1919,6 +2118,8 @@ "@opencode-ai/client": ["@opencode-ai/client@workspace:packages/client"], + "@opencode-ai/codemode": ["@opencode-ai/codemode@workspace:packages/codemode"], + "@opencode-ai/console-app": ["@opencode-ai/console-app@workspace:packages/console/app"], "@opencode-ai/console-core": ["@opencode-ai/console-core@workspace:packages/console/core"], @@ -1935,6 +2136,8 @@ "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], + "@opencode-ai/docs": ["@opencode-ai/docs@workspace:packages/docs"], + "@opencode-ai/effect-drizzle-sqlite": ["@opencode-ai/effect-drizzle-sqlite@workspace:packages/effect-drizzle-sqlite"], "@opencode-ai/effect-sqlite-node": ["@opencode-ai/effect-sqlite-node@workspace:packages/effect-sqlite-node"], @@ -1965,6 +2168,8 @@ "@opencode-ai/session-ui": ["@opencode-ai/session-ui@workspace:packages/session-ui"], + "@opencode-ai/simulation": ["@opencode-ai/simulation@workspace:packages/simulation"], + "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], "@opencode-ai/stats-app": ["@opencode-ai/stats-app@workspace:packages/stats/app"], @@ -2009,27 +2214,27 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - "@opentui/core": ["@opentui/core@0.3.4", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.3.4", "@opentui/core-darwin-x64": "0.3.4", "@opentui/core-linux-arm64": "0.3.4", "@opentui/core-linux-arm64-musl": "0.3.4", "@opentui/core-linux-x64": "0.3.4", "@opentui/core-linux-x64-musl": "0.3.4", "@opentui/core-win32-arm64": "0.3.4", "@opentui/core-win32-x64": "0.3.4" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-y0DlrChP9lcJ4jC5z/1wMS34+ygfSTW7gD5OJHwJaAScfmlFvuJOZbwmCGrJURZ+5wFBxuOi9LatZsmeAUIKAA=="], + "@opentui/core": ["@opentui/core@0.4.3", "", { "dependencies": { "bun-ffi-structs": "0.2.4", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.3", "@opentui/core-darwin-x64": "0.4.3", "@opentui/core-linux-arm64": "0.4.3", "@opentui/core-linux-arm64-musl": "0.4.3", "@opentui/core-linux-x64": "0.4.3", "@opentui/core-linux-x64-musl": "0.4.3", "@opentui/core-win32-arm64": "0.4.3", "@opentui/core-win32-x64": "0.4.3" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-rrJfAk13tALDqldYjhc78eWQ+aKq1iknJgffIOg3OwyZoqQo+p6gtuqyhmWvXIfQzlNUbpgpCPcxbXlhMnlaHQ=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4A7JYXUsZqhu9PPCe07E30ourSJYkitkwMujUyNKjM5e/dHNDVnz+5r5cO3M5snofLafc1DN7+9jEPn4UQzchQ=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p5+7AAxpxGuDGagyQfewKtmTFnN7THvTVY4FyKqUtJomNaHdQXPHztapNNzMx0DGWbwOUbVKzpL+yc3CZY3chQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jvm9E8n2sPhKEyKSXn9GlmJcj8WoJXJTooXb3djwjVaiimjihIj0XxHzCWhdqbDtQp+VxDFyCKoQagOOz20qhA=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-+fh0vEUE0lwVC7RW5ijYLRlTLp5NfvCRj8SzxDVd7IL2j2ssB6YXcfIbXq2EW7UGnrejwPRXf1tgUrIXW9KmOw=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-0uPuHCeZxm/O7+L+iNQl8zRAfehiwYstKkT9J0uTZO64/byBCLvy5lvn1DiE/72s/nTJ5nwpLN+pQs2/WYVKLQ=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-gl6qA5QJy6u8Cbt7gOtHbhhfMZ4qQDb0kEwFXHcMGmbnKzz4OHoq74D6tNjyvSQB9saoC7C6C0tvn2DcJOuNog=="], - "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-sJYUzYcSOb5PCXRlhwsse/fdsMiVomNvIwq/2TDhAANef+YPO3Br+OH9kQRbuj0bjVDmUS36SGYWSTFu2lUO+A=="], + "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-8p8g8/AEq/xFGpQ7XcIFKcAqjc0QwsZcv+Ll9RbCDpUA56FGH6jfLDir0KYTNTgYXJTIrBIENI9K46VuxMUMQA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-btYIQeNdPbN4JCrCjVB/RwMGrnRY7qWB2piNEfALSByuULKNjPKQ33PYIj38Yd01zCvCV7FotIeXEGSHx3tgCA=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-dXpJitiZdYE3hq2Pvx6e9I0uPQSOcnaLLp1pDgWAHv+3kvKSHEX//9Yr/pV/Ua6qqT7p+2D/K4vXNap/NKVo2w=="], - "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-fhmUey4oJJ2+N62xlIgAPxAl36Fa7wYffqDOT4QLpm0jfyD5xzo+wL/hr2zUqaEI439R8Iq6jHNxf/Nsx1WuuQ=="], + "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/QiFpCrpU2O7vy8QYmLIQYbvAtKDgmqcVjR7dGtqSzkiQk3ktNJoo5RozG7ueXnjung1Wp0nKldKxo2Csg/OrA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-sh432vPU+eLp8eA4I0KWKKn7D0VHbk01YTg6mA9/ihCNYHntc6LZ8/sLvsPv8CvKscMotfIkh3M5YhdS36BuXw=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Mx2zuOjrhm/z2SDS6RExIyjP/SnN/8QhhagxURUw0jQi/NssGSeAllu1cBAFFnhobJL5QLTE4FU4CRhUK9svgg=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-dw8FcjUZaLAjw25P3/7BarobCh/QOHn3srYaWYQdysoqyvSlPkQumpI8kV/KgpJtdITU1GW02MQC4EeLIFFalA=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.3", "", { "os": "win32", "cpu": "x64" }, "sha512-NuoqvWKGXaYnmlqvu7Gg2lLI6yVMnS9OfWBvxp+7Q+McSgHFSTQmYBXaPpvQ8HikpQXE1nCeMPtuSG4PdZHe2w=="], - "@opentui/keymap": ["@opentui/keymap@0.3.4", "", { "dependencies": { "@opentui/core": "0.3.4" }, "peerDependencies": { "@opentui/react": "0.3.4", "@opentui/solid": "0.3.4", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-8fo6BZWQgCjANfbKkzPo0ghAzS1E7TlHjDDS+SUhrX01qEUO1clFTRssKluHbXd2UJY1Ehle01TV5bFmY78f8w=="], + "@opentui/keymap": ["@opentui/keymap@0.4.3", "", { "dependencies": { "@opentui/core": "0.4.3" }, "peerDependencies": { "@opentui/react": "0.4.3", "@opentui/solid": "0.4.3", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-sinX0pyQBRrEvo89PSSUbSUDIYpL3xWo81VEfec58VFoVRB5FG48/deAtvRTQfJ8w1kgbzN8hzdOXdSm61zBmw=="], - "@opentui/solid": ["@opentui/solid@0.3.4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.3.4", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-gin1VnsVBahX0nrU3mpgh5U1qvyJBIZu4NE5mc0YnObWOEf9HVNxKY4/BpUvQPh91kT6zeOzTBvAvYK4R7g9MQ=="], + "@opentui/solid": ["@opentui/solid@0.4.3", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.4.3", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-RcV0+S8HMdXOASyr7HmJUBuTUIaFPzAxMDa44VftS5C2JUgrmAuWo0Njv1q3TWRB1owjHnyKhEfWGKq7A82wxw=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -2111,7 +2316,7 @@ "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="], - "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + "@oxc-project/types": ["@oxc-project/types@0.138.0", "", {}, "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="], @@ -2305,6 +2510,8 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@posthog/core": ["@posthog/core@1.7.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA=="], + "@preact/signals-core": ["@preact/signals-core@1.14.3", "", {}, "sha512-m0K3vnbSLC5rHs2ZVfeAMvBtT1zIyq4mxx5OlNncSgMj5Iz6W5Rn3kPrDxAC+iIKmiVe0lSl6U37t5ZkEWoVAw=="], "@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="], @@ -2335,6 +2542,8 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + "@puppeteer/browsers": ["@puppeteer/browsers@2.3.0", "", { "dependencies": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA=="], + "@radix-ui/colors": ["@radix-ui/colors@1.0.1", "", {}, "sha512-xySw8f0ZVsAEP+e7iLl3EvcBXX7gsIlC1Zso/sPBW9gIWerBTgz6axrjU+MZ39wD+WFi5h5zdWpsg3+hwt2Qsg=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw=="], @@ -2399,7 +2608,37 @@ "@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.4", "", { "os": "android", "cpu": "arm64" }, "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.4", "", { "os": "linux", "cpu": "arm" }, "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.4", "", { "os": "none", "cpu": "arm64" }, "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.4", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], @@ -2453,6 +2692,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], + "@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="], + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="], @@ -2509,6 +2750,8 @@ "@shikijs/transformers": ["@shikijs/transformers@3.9.2", "", { "dependencies": { "@shikijs/core": "3.9.2", "@shikijs/types": "3.9.2" } }, "sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA=="], + "@shikijs/twoslash": ["@shikijs/twoslash@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/types": "3.23.0", "twoslash": "^0.3.6" }, "peerDependencies": { "typescript": ">=5.5.0" } }, "sha512-pNaLJWMA3LU7PhT8tm9OQBZ1epy0jmdgeJzntBtr1EVXLbHxGzTj3mnf9vOdcl84l96qnlJXkJ/NGXZYBpXl5g=="], + "@shikijs/types": ["@shikijs/types@3.9.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -2529,6 +2772,10 @@ "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + "@sindresorhus/slugify": ["@sindresorhus/slugify@2.2.0", "", { "dependencies": { "@sindresorhus/transliterate": "^1.0.0", "escape-string-regexp": "^5.0.0" } }, "sha512-9Vybc/qX8Kj6pxJaapjkFbiUJPk7MAkCh/GFCxIBnnsuYCFPIXKvnLidG8xlepht3i24L5XemUmGtrJ3UWrl6w=="], + + "@sindresorhus/transliterate": ["@sindresorhus/transliterate@1.6.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0" } }, "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ=="], + "@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="], "@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="], @@ -2645,6 +2892,8 @@ "@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="], + "@solid-primitives/memo": ["@solid-primitives/memo@1.5.1", "", { "dependencies": { "@solid-primitives/scheduled": "^1.5.3", "@solid-primitives/utils": "^6.4.1" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-VDPrkl9epp0tbby9MvsqphGFCYCtDRC5J8FKzTqHbQiG5hhR8n6xv4MfjhTW231IaBxxPHLxS43EE8c5Q23mSQ=="], + "@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="], "@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="], @@ -2683,6 +2932,36 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@stoplight/better-ajv-errors": ["@stoplight/better-ajv-errors@1.0.3", "", { "dependencies": { "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA=="], + + "@stoplight/json": ["@stoplight/json@3.21.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.3", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "jsonc-parser": "~2.2.1", "lodash": "^4.17.21", "safe-stable-stringify": "^1.1" } }, "sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g=="], + + "@stoplight/json-ref-readers": ["@stoplight/json-ref-readers@1.2.2", "", { "dependencies": { "node-fetch": "^2.6.0", "tslib": "^1.14.1" } }, "sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ=="], + + "@stoplight/json-ref-resolver": ["@stoplight/json-ref-resolver@3.1.6", "", { "dependencies": { "@stoplight/json": "^3.21.0", "@stoplight/path": "^1.3.2", "@stoplight/types": "^12.3.0 || ^13.0.0", "@types/urijs": "^1.19.19", "dependency-graph": "~0.11.0", "fast-memoize": "^2.5.2", "immer": "^9.0.6", "lodash": "^4.17.21", "tslib": "^2.6.0", "urijs": "^1.19.11" } }, "sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A=="], + + "@stoplight/ordered-object-literal": ["@stoplight/ordered-object-literal@1.0.5", "", {}, "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg=="], + + "@stoplight/path": ["@stoplight/path@1.3.2", "", {}, "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ=="], + + "@stoplight/spectral-core": ["@stoplight/spectral-core@1.23.1", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "~3.21.0", "@stoplight/path": "1.3.2", "@stoplight/spectral-parsers": "^1.0.0", "@stoplight/spectral-ref-resolver": "^1.0.4", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "~13.6.0", "@types/es-aggregate-error": "^1.0.2", "@types/json-schema": "^7.0.11", "ajv": "^8.18.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "es-aggregate-error": "^1.0.7", "expr-eval-fork": "^3.0.1", "jsonpath-plus": "^10.3.0", "lodash": "^4.18.1", "lodash.topath": "^4.5.2", "minimatch": "^3.1.4", "nimma": "0.2.3", "pony-cause": "^1.1.1", "tslib": "^2.8.1" } }, "sha512-VLC8OhpO/pMJKb6IHhurxJjXO1qB56Ng1unIb8b+hNxdw0+SEcASvmR+RpjfHYX/jv/DfSaA1x8QhFBJBmqBOQ=="], + + "@stoplight/spectral-formats": ["@stoplight/spectral-formats@1.8.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/json": "^3.17.0", "@stoplight/spectral-core": "^1.23.0", "@types/json-schema": "^7.0.7", "tslib": "^2.8.1" } }, "sha512-xaC0rCH0p7/bzNJsz+JgLSj+Cp6uwYGWpePQxdLkF2G6a8Zyp3OyS7umkGYNiimEwKrOjvCNNTFJpeuiENZSBA=="], + + "@stoplight/spectral-functions": ["@stoplight/spectral-functions@1.10.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "^3.17.1", "@stoplight/spectral-core": "^1.23.0", "@stoplight/spectral-formats": "^1.8.1", "@stoplight/spectral-runtime": "^1.1.2", "ajv": "^8.18.0", "ajv-draft-04": "~1.0.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "lodash": "^4.18.1", "tslib": "^2.8.1" } }, "sha512-vDCd0NJ93715bcUpZZ5vNHiyxd4cgHF6tuXsDiXOXKAByg+I1fR5/dMijEo6Ce1Lz95a+RZ22JKYhF1YuzVvuA=="], + + "@stoplight/spectral-parsers": ["@stoplight/spectral-parsers@1.0.5", "", { "dependencies": { "@stoplight/json": "~3.21.0", "@stoplight/types": "^14.1.1", "@stoplight/yaml": "~4.3.0", "tslib": "^2.8.1" } }, "sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ=="], + + "@stoplight/spectral-ref-resolver": ["@stoplight/spectral-ref-resolver@1.0.5", "", { "dependencies": { "@stoplight/json-ref-readers": "1.2.2", "@stoplight/json-ref-resolver": "~3.1.6", "@stoplight/spectral-runtime": "^1.1.2", "dependency-graph": "0.11.0", "tslib": "^2.8.1" } }, "sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA=="], + + "@stoplight/spectral-runtime": ["@stoplight/spectral-runtime@1.1.6", "", { "dependencies": { "@stoplight/json": "^3.20.1", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "lodash": "^4.18.1", "node-fetch": "^2.7.0", "tslib": "^2.8.1" } }, "sha512-Y8rEDyMN4bSMJCrDs2shdcVHYyCnH3FvXRP4dBhha4Z8iJv+JPp7KqOV/hwVB/hWFC209upiwj2oDmLfR0qCDg=="], + + "@stoplight/types": ["@stoplight/types@13.20.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA=="], + + "@stoplight/yaml": ["@stoplight/yaml@4.3.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.5", "@stoplight/types": "^14.1.1", "@stoplight/yaml-ast-parser": "0.0.50", "tslib": "^2.2.0" } }, "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w=="], + + "@stoplight/yaml-ast-parser": ["@stoplight/yaml-ast-parser@0.0.50", "", {}, "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ=="], + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.4.1", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.4.1" } }, "sha512-MGft/IXjJ20a9KbaSVG9bHTAAoanbucKrgEiJJRNqpim8DsXA01+XTdSk17LmiOCB203Rrq9mWgdQ6+79cc8iA=="], "@storybook/addon-docs": ["@storybook/addon-docs@10.4.1", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.4.1", "@storybook/icons": "^2.0.2", "@storybook/react-dom-shim": "10.4.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.1" }, "optionalPeers": ["@types/react"] }, "sha512-IYqUdjoZe4VO2LFZlKL/gwy7DsQSWCq6hX+zc1MBmZo04yycDASk1tte57n9pdlW3ajw9yYMF/+lVBi+xQjyvw=="], @@ -2761,6 +3040,8 @@ "@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -2769,8 +3050,22 @@ "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + "@turbo/darwin-64": ["@turbo/darwin-64@2.10.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-wBM3ObqOWnKUDmg7QfUFDkDHPFUAJmrYlYqmEM8jMPAPA/I6wRJIbWimeQUqhOiQ8xPKhzyWM+xaiUP0wz8FEQ=="], + + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.10.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/Cq0joWnuMjDPfhjbFP4sv+C/7gkQ415zlaO4XUzD5EZxbtrKgXKvuuydMvogG8GeUnN1aDltW71RlmEfpjbyw=="], + + "@turbo/linux-64": ["@turbo/linux-64@2.10.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mMsf5IIhiKuceEXNstd25IbadjBXZ0amxzFOqliEzJX6HyeeHdBQPVSY583PWqYDyqM/FB8d5ZjkthfBSeuH3Q=="], + + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.10.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Wcng1i2kaKmXutmwxT9MUoYZvdaIekXAdlGr4+0TpgbhGLw7nDuEcRBFrxb5BbRoX1d1q8SpdRxLc45TvDZIdQ=="], + + "@turbo/windows-64": ["@turbo/windows-64@2.10.2", "", { "os": "win32", "cpu": "x64" }, "sha512-SsNhM7Ho7EpAdwtrJKBOic9Hso23vu6Dp0gAfLOvUFjPzurr/sGQlXZEvr6z89ne4RDOypTwz5CBDrixpMKtXw=="], + + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.10.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gf+S7ICAdimT/n02bOuVWKvhHnct/HYjZg3oBNIz5hZ9ZyWHbQim9J3P5Qip8WpX0ksxF7eaBVziJCuLnjhqDg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + "@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -2795,6 +3090,8 @@ "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], + "@types/cross-spawn": ["@types/cross-spawn@6.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA=="], "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], @@ -2807,6 +3104,8 @@ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -2883,6 +3182,8 @@ "@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], @@ -2913,6 +3214,8 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/urijs": ["@types/urijs@1.19.26", "", {}, "sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg=="], + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], @@ -2953,6 +3256,12 @@ "@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="], + "@vercel/cli-config": ["@vercel/cli-config@0.2.0", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-fJRRRB7734BDuXZ89yBEaA2ncYhH7bWX30mk04W80J6VAfQc+4iB8lyzAdaGpFV3/vNlkt9VZt+/uoQoWX6UsQ=="], + + "@vercel/cli-exec": ["@vercel/cli-exec@1.0.0", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug=="], + + "@vercel/functions": ["@vercel/functions@3.7.5", "", { "dependencies": { "@vercel/oidc": "3.8.0" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*", "ws": ">=8" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity", "ws"] }, "sha512-ESf8BbeDebqRUyMi09JwRbQqpLn4g6fjcVVHPsHB56j2dSqRrSHO4h3X4aaxJf6iQQjzhAtDGI2xCWQ27JE8PA=="], + "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], @@ -3003,18 +3312,24 @@ "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], - "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], + "address": ["address@1.2.2", "", {}, "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA=="], + + "adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "aggregate-error": ["aggregate-error@4.0.1", "", { "dependencies": { "clean-stack": "^4.0.0", "indent-string": "^5.0.0" } }, "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w=="], + "ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="], "ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="], @@ -3023,6 +3338,8 @@ "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + "ajv-errors": ["ajv-errors@3.0.0", "", { "peerDependencies": { "ajv": "^8.0.1" } }, "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="], + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], @@ -3031,6 +3348,8 @@ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3059,6 +3378,10 @@ "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + "arkregex": ["arkregex@0.0.3", "", { "dependencies": { "@ark/util": "0.55.0" } }, "sha512-bU21QJOJEFJK+BPNgv+5bVXkvRxyAvgnon75D92newgHxkBJTgiFwQxusyViYyJkETsddPlHyspshDQcCzmkNg=="], + + "arktype": ["arktype@2.1.27", "", { "dependencies": { "@ark/schema": "0.55.0", "@ark/util": "0.55.0", "arkregex": "0.0.3" } }, "sha512-enctOHxI4SULBv/TDtCVi5M8oLd4J5SVlPUblXDzSsOYQNMzmVbUosGBnJuZDKmFlN5Ie0/QVEuTE+Z5X1UhsQ=="], + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], @@ -3103,10 +3426,14 @@ "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="], + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "autoprefixer": ["autoprefixer@10.5.0", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "avsc": ["avsc@5.7.9", "", {}, "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg=="], + "avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="], "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], @@ -3151,14 +3478,20 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="], + "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], + "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], + "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], "bin-links": ["bin-links@6.0.2", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], @@ -3167,13 +3500,15 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], - "body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="], + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], @@ -3209,7 +3544,7 @@ "builder-util-runtime": ["builder-util-runtime@9.7.0", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw=="], - "bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="], + "bun-ffi-structs": ["bun-ffi-structs@0.2.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg=="], "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], @@ -3237,6 +3572,8 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], @@ -3261,6 +3598,8 @@ "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "chardet": ["chardet@2.2.0", "", {}, "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA=="], + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], @@ -3273,6 +3612,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "chromium-bidi": ["chromium-bidi@0.6.2", "", { "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-4WVBa6ijmUTVr9cZD4eicQD8Mdy/HCX3bzEIYYpmk0glqYLoWH+LqQEvV9RpDRzoQSbY1KJHloYXbDMXMbDPhg=="], + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], @@ -3283,12 +3624,18 @@ "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="], + "clean-stack": ["clean-stack@4.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg=="], + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + "clipboardy": ["clipboardy@4.0.0", "", { "dependencies": { "execa": "^8.0.1", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w=="], "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], @@ -3305,10 +3652,14 @@ "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], + "color-blend": ["color-blend@4.0.0", "", {}, "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -3341,22 +3692,26 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-es": ["cookie-es@2.0.1", "", {}, "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA=="], - "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + "cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], @@ -3383,6 +3738,8 @@ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], @@ -3417,12 +3774,18 @@ "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], + "decode-bmp": ["decode-bmp@0.2.1", "", { "dependencies": { "@canvas/image-data": "^1.0.0", "to-data-view": "^1.1.0" } }, "sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA=="], + + "decode-ico": ["decode-ico@0.4.1", "", { "dependencies": { "@canvas/image-data": "^1.0.0", "decode-bmp": "^0.2.0", "to-data-view": "^1.1.0" } }, "sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -3439,12 +3802,16 @@ "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dependency-graph": ["dependency-graph@0.11.0", "", {}, "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg=="], + "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], @@ -3453,18 +3820,22 @@ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], - "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "detect-port": ["detect-port@1.5.1", "", { "dependencies": { "address": "^1.0.1", "debug": "4" }, "bin": { "detect": "bin/detect-port.js", "detect-port": "bin/detect-port.js" } }, "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ=="], + "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "devtools-protocol": ["devtools-protocol@0.0.1312386", "", {}, "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA=="], + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], "diacritics": ["diacritics@1.3.0", "", {}, "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA=="], @@ -3487,6 +3858,8 @@ "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], + "dns-socket": ["dns-socket@4.2.2", "", { "dependencies": { "dns-packet": "^5.2.4" } }, "sha512-BDeBd8najI4/lS00HSKpdFia+OvUMytaVjfzR9n5Lq8MlZRSvtbI+uLtx1+XmQFls5wFU9dssccTmQQ6nfpjdg=="], + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -3569,6 +3942,8 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "engine.io": ["engine.io@6.6.9", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0" } }, "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg=="], + "engine.io-client": ["engine.io-client@6.6.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg=="], "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], @@ -3579,14 +3954,20 @@ "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], + "es-aggregate-error": ["es-aggregate-error@1.0.14", "", { "dependencies": { "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "globalthis": "^1.0.4", "has-property-descriptors": "^1.0.2", "set-function-name": "^2.0.2" } }, "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA=="], + "es-array-method-boxes-properly": ["es-array-method-boxes-properly@1.0.0", "", {}, "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -3603,6 +3984,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], @@ -3621,8 +4004,12 @@ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], @@ -3637,6 +4024,8 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], @@ -3655,11 +4044,15 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], - "express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="], + "expr-eval-fork": ["expr-eval-fork@3.0.3", "", {}, "sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg=="], + + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="], @@ -3693,6 +4086,8 @@ "fast-json-stringify": ["fast-json-stringify@6.4.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ=="], + "fast-memoize": ["fast-memoize@2.5.2", "", {}, "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="], + "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], @@ -3707,6 +4102,10 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + + "favicons": ["favicons@7.2.0", "", { "dependencies": { "escape-html": "^1.0.3", "sharp": "^0.33.1", "xml2js": "^0.6.1" } }, "sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -3717,7 +4116,7 @@ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="], @@ -3745,6 +4144,8 @@ "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], @@ -3753,9 +4154,15 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + "fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="], + "framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="], - "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "front-matter": ["front-matter@4.0.2", "", { "dependencies": { "js-yaml": "^3.13.1" } }, "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], @@ -3775,6 +4182,8 @@ "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + "gcd": ["gcd@0.0.1", "", {}, "sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw=="], + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], @@ -3801,13 +4210,17 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#513463a", {}, "anomalyco-ghostty-web-513463a", "sha512-GZR8LSmgGzViWnBJrqRI8MpAZRCJxhcr1Hi9Tyeh7YRooHZQjK9J97FQRD3tbBaM2wjq05gzGY2UEsG+JtZeBw=="], "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], - "gitlab-ai-provider": ["gitlab-ai-provider@6.9.3", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-lWo6b6es5+k9iXaDIvE9ECzyK4zfEza4+dQ5FN8SJpEuVRi3ZBCpHIOTa32QoYEDCBaiPh+tcyca86PfNodmlg=="], + "gitlab-ai-provider": ["gitlab-ai-provider@6.10.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-oWEZ06rDO6JjB7INHO882wyBAQqCZVHiDHwCs5M+VPmdDj8TzhGXcYesA2CcV5RoI5lfHLKwGp5uKFB62VWpqw=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], @@ -3861,8 +4274,12 @@ "hast-util-format": ["hast-util-format@1.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-whitespace": "^3.0.0", "html-whitespace-sensitive-tag-names": "^3.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="], + "hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="], + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + "hast-util-from-html-isomorphic": ["hast-util-from-html-isomorphic@2.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-dom": "^5.0.0", "hast-util-from-html": "^2.0.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw=="], + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], "hast-util-has-property": ["hast-util-has-property@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA=="], @@ -3889,6 +4306,8 @@ "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + "hast-util-to-mdast": ["hast-util-to-mdast@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-to-html": "^9.0.0", "hast-util-to-text": "^4.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "mdast-util-to-string": "^4.0.0", "rehype-minify-whitespace": "^6.0.0", "trim-trailing-lines": "^2.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-DsL/SvCK9V7+vfc6SLQ+vKIyBDXTk2KLSbfBYkH4zeF/uR1yBajHRhkzuaUSGOB1WJSTieJBdHwxlC+HLKvZZw=="], + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], @@ -3903,6 +4322,8 @@ "heap-snapshot-toolkit": ["heap-snapshot-toolkit@1.1.3", "", {}, "sha512-joThu2rEsDu8/l4arupRDI1qP4CZXNG+J6Wr348vnbLGSiBkwRdqZ6aOHl5BzEiC+Dc8OTbMlmWjD0lbXD5K2Q=="], + "hex-rgb": ["hex-rgb@5.0.0", "", {}, "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w=="], + "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], @@ -3947,6 +4368,8 @@ "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + "ico-endec": ["ico-endec@0.1.6", "", {}, "sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ=="], + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -3959,6 +4382,8 @@ "immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], @@ -3971,8 +4396,14 @@ "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "ink": ["ink@6.3.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-2CbJAa7XeziZYe6pDS5RVLirRY28iSGMQuEV8jRU5NQsONQNfcR/BZHHc9vkMg2lGYTHTM2pskxC1YmY28p6bQ=="], + + "ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + "inquirer": ["inquirer@12.3.0", "", { "dependencies": { "@inquirer/core": "^10.1.2", "@inquirer/prompts": "^7.2.1", "@inquirer/type": "^3.0.2", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", "run-async": "^3.0.0", "rxjs": "^7.8.1" }, "peerDependencies": { "@types/node": ">=18" } }, "sha512-3NixUXq+hM8ezj2wc7wC37b32/rHq1MwNZDYdvx+d6jokOD+r+i8Q4Pkylh9tISYP114A128LCX8RKhopC5RfQ=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -3981,6 +4412,8 @@ "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], + "ip-regex": ["ip-regex@4.3.0", "", {}, "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], @@ -4033,10 +4466,14 @@ "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-ip": ["is-ip@3.1.0", "", { "dependencies": { "ip-regex": "^4.0.0" } }, "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -4045,6 +4482,8 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-online": ["is-online@10.0.0", "", { "dependencies": { "got": "^12.1.0", "p-any": "^4.0.0", "p-timeout": "^5.1.0", "public-ip": "^5.0.0" } }, "sha512-WCPdKwNDjXJJmUubf2VHLMDBkUZEtuOvpXUfUnUFbEnM6In9ByiScL4f4jKACz/fsb2qDkesFerW3snf/AYz3A=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -4117,6 +4556,8 @@ "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="], + "jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], @@ -4149,6 +4590,10 @@ "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonpath-plus": ["jsonpath-plus@10.4.0", "", { "dependencies": { "@jsep-plugin/assignment": "^1.3.0", "@jsep-plugin/regex": "^1.0.4", "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" } }, "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA=="], + + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], @@ -4163,6 +4608,8 @@ "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], @@ -4181,8 +4628,12 @@ "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + "lcm": ["lcm@0.0.3", "", { "dependencies": { "gcd": "^0.0.1" } }, "sha512-TB+ZjoillV6B26Vspf9l2L/vKaRY/4ep3hahcyVkCGFgsTNRUQdc24bQeNFiZeoxH0vr5+7SfNRMQuPHv/1IrQ=="], + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "leven": ["leven@4.1.0", "", {}, "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew=="], + "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], @@ -4233,6 +4684,8 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.topath": ["lodash.topath@4.5.2", "", {}, "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg=="], + "loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -4289,6 +4742,8 @@ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], @@ -4301,6 +4756,8 @@ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + "mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], + "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], @@ -4319,11 +4776,11 @@ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="], - "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], @@ -4337,6 +4794,8 @@ "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="], + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], @@ -4351,9 +4810,11 @@ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + "micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="], + "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="], - "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.1", "", { "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg=="], "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], @@ -4439,8 +4900,14 @@ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "mint": ["mint@4.2.666", "", { "dependencies": { "@mintlify/cli": "4.0.1269" }, "bin": { "mint": "index.js" } }, "sha512-FsdL35EH++MiVDoKxN8M6/obOsrgx0Ko7P/1Y2lBXh/jEPx1UD1Y8msmAOW6cuwaqGIzAxuC7ySmr7D3G2bmBg=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], @@ -4465,6 +4932,8 @@ "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], + "mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -4475,12 +4944,20 @@ "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], + + "next-mdx-remote-client": ["next-mdx-remote-client@1.1.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "@types/mdx": "^2.0.13", "remark-mdx-remove-esm": "^1.3.2", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 18.3.0 < 19.0.0", "react-dom": ">= 18.3.0 < 19.0.0" } }, "sha512-IElOrn02JjGQZxx+re7wMx/1AUG+Arte9aDImAtxjAfMw6xuSCaH5mTCunKelkWzFyFdRb565jO8jRICvvh96g=="], + "nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="], + "nimma": ["nimma@0.2.3", "", { "dependencies": { "@jsep-plugin/regex": "^1.0.1", "@jsep-plugin/ternary": "^1.0.2", "astring": "^1.8.1", "jsep": "^1.2.0" }, "optionalDependencies": { "jsonpath-plus": "^6.0.1 || ^10.1.0", "lodash.topath": "^4.5.2" } }, "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA=="], + "nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="], "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], @@ -4513,6 +4990,8 @@ "node-releases": ["node-releases@2.0.46", "", {}, "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ=="], + "non-error": ["non-error@0.1.0", "", {}, "sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ=="], + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -4539,6 +5018,8 @@ "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], + "oauth4webapi": ["oauth4webapi@3.8.6", "", {}, "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], @@ -4585,6 +5066,8 @@ "opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="], + "os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="], @@ -4599,6 +5082,8 @@ "oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="], + "p-any": ["p-any@4.0.0", "", { "dependencies": { "p-cancelable": "^3.0.0", "p-some": "^6.0.0" } }, "sha512-S/B50s+pAVe0wmEZHmBs/9yJXeZ5KhHzOsgKzt0hRdgkoR3DxW9ts46fcsWi/r3VnzsnkKS7q4uimze+zjdryw=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], "p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="], @@ -4615,10 +5100,16 @@ "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + "p-some": ["p-some@6.0.0", "", { "dependencies": { "aggregate-error": "^4.0.0", "p-cancelable": "^3.0.0" } }, "sha512-CJbQCKdfSX3fIh8/QKgS+9rjm7OBNUTmwWswAFQAhc8j1NR1dsEDETUEuVUtQHZpV+J03LqWBEwvu0g1Yn+TYg=="], + "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], @@ -4631,10 +5122,14 @@ "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -4649,6 +5144,8 @@ "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -4713,6 +5210,8 @@ "poe-oauth": ["poe-oauth@0.0.8", "", {}, "sha512-zlaRVLR6vuxBIYUkZoTIVo3f8h3qd27gv9Ms+kmGiYEiiV4TdccddTdNcGyI0DnuJ9tVi+5LP3Bvzez59IFbjw=="], + "pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], @@ -4733,6 +5232,8 @@ "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "posthog-node": ["posthog-node@5.17.2", "", { "dependencies": { "@posthog/core": "1.7.1" } }, "sha512-lz3YJOr0Nmiz0yHASaINEDHqoV+0bC3eD8aZAG+Ky292dAnVYul+ga/dMX8KCBXg8hHfKdxw0SztYD5j6dgUqQ=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], @@ -4741,6 +5242,8 @@ "preact-render-to-string": ["preact-render-to-string@6.6.5", "", { "peerDependencies": { "preact": ">=10 || >= 11.0.0-0" } }, "sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -4781,14 +5284,22 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "public-ip": ["public-ip@5.0.0", "", { "dependencies": { "dns-socket": "^4.2.2", "got": "^12.0.0", "is-ip": "^3.1.0" } }, "sha512-xaH3pZMni/R2BG7ZXXaWS9Wc9wFlhyDVJF47IJ+3ali0TGv+2PsckKxbmo+rnx3ZxiV2wblVhtdS3bohAP6GGw=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pupa": ["pupa@3.3.0", "", { "dependencies": { "escape-goat": "^4.0.0" } }, "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA=="], + "puppeteer": ["puppeteer@22.14.0", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1312386", "puppeteer-core": "22.14.0" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" } }, "sha512-MGTR6/pM8zmWbTdazb6FKnwIihzsSEXBPH49mFFU96DNZpQOevCAZMnjBZGlZRGRzRK6aADCavR6SQtrbv5dQw=="], + + "puppeteer-core": ["puppeteer-core@22.14.0", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "chromium-bidi": "0.6.2", "debug": "^4.3.5", "devtools-protocol": "0.0.1312386", "ws": "^8.18.0" } }, "sha512-rl4tOY5LcA3e374GAlsGGHc05HL3eGNf5rZ+uxkl6id9zVZKcwcp1Z+Nd6byb6WPiPeecT/dwz8f/iUm+AZQSw=="], + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], @@ -4809,7 +5320,9 @@ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], @@ -4821,6 +5334,8 @@ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], "react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="], @@ -4881,6 +5396,10 @@ "rehype-format": ["rehype-format@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" } }, "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="], + "rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="], + + "rehype-minify-whitespace": ["rehype-minify-whitespace@6.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0" } }, "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw=="], + "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], @@ -4891,11 +5410,19 @@ "relateurl": ["relateurl@0.2.7", "", {}, "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="], + "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], + "remark-directive": ["remark-directive@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" } }, "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A=="], + "remark-frontmatter": ["remark-frontmatter@5.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0", "unified": "^11.0.0" } }, "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ=="], + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="], + + "remark-mdx": ["remark-mdx@3.1.0", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA=="], + + "remark-mdx-remove-esm": ["remark-mdx-remove-esm@1.3.2", "", { "dependencies": { "@types/mdast": "^4.0.4", "unist-util-remove": "^4.0.0" }, "peerDependencies": { "unified": "^11" } }, "sha512-BvL8VSdVXy9S7NlHP56nUJAHFc45h5E9HnHiLUGHe5tw3Yvm/3cVZvAzlkEEh2i+fkq2uKrf2xn5VmItBhMypA=="], "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -4931,6 +5458,8 @@ "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], @@ -4953,6 +5482,8 @@ "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "rolldown": ["rolldown@1.1.4", "", { "dependencies": { "@oxc-project/types": "=0.138.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.4", "@rolldown/binding-darwin-arm64": "1.1.4", "@rolldown/binding-darwin-x64": "1.1.4", "@rolldown/binding-freebsd-x64": "1.1.4", "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", "@rolldown/binding-linux-arm64-gnu": "1.1.4", "@rolldown/binding-linux-arm64-musl": "1.1.4", "@rolldown/binding-linux-ppc64-gnu": "1.1.4", "@rolldown/binding-linux-s390x-gnu": "1.1.4", "@rolldown/binding-linux-x64-gnu": "1.1.4", "@rolldown/binding-linux-x64-musl": "1.1.4", "@rolldown/binding-openharmony-arm64": "1.1.4", "@rolldown/binding-wasm32-wasi": "1.1.4", "@rolldown/binding-win32-arm64-msvc": "1.1.4", "@rolldown/binding-win32-x64-msvc": "1.1.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA=="], + "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -4961,8 +5492,12 @@ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + "run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="], "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], @@ -4995,7 +5530,7 @@ "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], - "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], @@ -5005,7 +5540,7 @@ "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], - "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], @@ -5019,6 +5554,8 @@ "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + "sharp-ico": ["sharp-ico@0.1.5", "", { "dependencies": { "decode-ico": "*", "ico-endec": "*", "sharp": "*" } }, "sha512-a3jODQl82NPp1d5OYb0wY+oFaPk7AvyxipIowCHk7pBsZCWgbe0yAkU2OOXdoH0ENyANhyOQbs9xkAiRHcF02Q=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -5041,6 +5578,10 @@ "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], @@ -5057,6 +5598,10 @@ "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], + "socket.io": ["socket.io@4.8.0", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA=="], + + "socket.io-adapter": ["socket.io-adapter@2.5.8", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.21.0" } }, "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw=="], + "socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], "socket.io-parser": ["socket.io-parser@4.2.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg=="], @@ -5065,11 +5610,15 @@ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "solid-dismissible": ["solid-dismissible@0.1.1", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA=="], + + "solid-focus-trap": ["solid-focus-trap@0.1.9", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-LTyNki6GUJPRLXV5uMWPkYClB07SUMubbr2EkAddiR0CJCF/I283txilMU9RURSr/P8EewMfXWu2o3aWrK7A5A=="], + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], "solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="], - "solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="], + "solid-presence": ["solid-presence@0.2.0", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-YM92o+jvpzX3XGaD4rLYmq/Kc2ZVh47GSCLEufHBFQQIurvZTs8SoGJxO8BJGNDxBKdcS8F3dYhW1SDXp4BNjA=="], "solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="], @@ -5077,6 +5626,8 @@ "solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="], + "solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="], + "solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="], "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], @@ -5127,6 +5678,8 @@ "sst-win32-x86": ["sst-win32-x86@4.13.1", "", { "os": "win32", "cpu": "none" }, "sha512-YPxBVdac/MsrzwlC6pF0NrrvMcmfdBLYjv7MbzHc5jNh1FQ1WPh6bdWQqgv0KD9EQTNLLEkej0beydgUvcCWJg=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], @@ -5175,6 +5728,8 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -5209,6 +5764,8 @@ "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "tar-fs": ["tar-fs@2.1.5", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw=="], + "tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="], "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], @@ -5229,6 +5786,8 @@ "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], @@ -5255,6 +5814,8 @@ "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-data-view": ["to-data-view@1.1.0", "", {}, "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toad-cache": ["toad-cache@3.7.1", "", {}, "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ=="], @@ -5279,6 +5840,8 @@ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "trim-trailing-lines": ["trim-trailing-lines@2.1.0", "", {}, "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg=="], + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], @@ -5299,27 +5862,23 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo-darwin-64": ["turbo-darwin-64@2.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="], + "turbo": ["turbo@2.10.2", "", { "optionalDependencies": { "@turbo/darwin-64": "2.10.2", "@turbo/darwin-arm64": "2.10.2", "@turbo/linux-64": "2.10.2", "@turbo/linux-arm64": "2.10.2", "@turbo/windows-64": "2.10.2", "@turbo/windows-arm64": "2.10.2" }, "bin": { "turbo": "bin/turbo" } }, "sha512-wTExrNrRjB8qzIcg+ZLm0A3GFNLDsWNwdS/RBXB0FPrBDyzk3i96Yx+TxWZC7a0k1SIreFB8ciUbxjmEqTH8IQ=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ=="], - - "turbo-linux-64": ["turbo-linux-64@2.8.13", "", { "os": "linux", "cpu": "x64" }, "sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw=="], + "turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.8.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - "turbo-windows-64": ["turbo-windows-64@2.8.13", "", { "os": "win32", "cpu": "x64" }, "sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA=="], + "tw-to-css": ["tw-to-css@0.0.12", "", { "dependencies": { "postcss": "8.4.31", "postcss-css-variables": "0.18.0", "tailwindcss": "3.3.2" } }, "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.8.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA=="], + "twoslash": ["twoslash@0.3.9", "", { "dependencies": { "@typescript/vfs": "^1.6.4", "twoslash-protocol": "0.3.9" }, "peerDependencies": { "typescript": "^5.5.0 || ^6.0.0" } }, "sha512-rDclk+OtzuTX+tnea7DYLCkqGQ3eP0IyfD+kzUJ7t46X/NzlaxwrhecmEBNuSCuEn3V+n1PhcjUUQQ7gUJzX5Q=="], - "turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="], - - "tw-to-css": ["tw-to-css@0.0.12", "", { "dependencies": { "postcss": "8.4.31", "postcss-css-variables": "0.18.0", "tailwindcss": "3.3.2" } }, "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw=="], + "twoslash-protocol": ["twoslash-protocol@0.3.9", "", {}, "sha512-9/iwp+CXOnjFMPQuPL5PkuRbZnDoNpBvtJCLs9t8kDYkL3YHujbvnHfZA1i5fApDftVEdBw+T/4F+dH5kIzpYQ=="], "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], - "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], @@ -5345,6 +5904,8 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="], + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], @@ -5361,16 +5922,22 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], + "unist-builder": ["unist-builder@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg=="], + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + "unist-util-map": ["unist-util-map@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-HJs1tpkSmRJUzj6fskQrS5oYhBYlmtcvy4SepdDEEsL04FjBrgF0Mgggvxc1/qGBGgW7hRh9+UBK1aqTEnBpIA=="], + "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + "unist-util-remove": ["unist-util-remove@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg=="], + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], @@ -5401,8 +5968,14 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "uqr": ["uqr@0.1.3", "", {}, "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="], + + "urlpattern-polyfill": ["urlpattern-polyfill@10.0.0", "", {}, "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -5413,6 +5986,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="], + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], @@ -5423,7 +5998,7 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.0.2", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.47", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.27" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-aoa05nI3BTK5aGbjBflq+Gfln2AHAkwNbWuGGvCzUIsOfp5Y3iPD4O4PUGDAEiWVJWbjpPn0KfDa0H/HebwsaA=="], + "venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.1.1", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.51", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.30" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-w3OHkuzzKZ3r2TOxER6myBYzZJNoDqol+DUHu3NnfBN/GETnUVxecZJab0CHQQ8GZc0jjzpFymepjcLDPS4SQg=="], "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], @@ -5431,6 +6006,8 @@ "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + "vfile-matter": ["vfile-matter@5.0.1", "", { "dependencies": { "vfile": "^6.0.0", "yaml": "^2.0.0" } }, "sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw=="], + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], "vite": ["vite@7.1.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw=="], @@ -5535,8 +6112,12 @@ "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + "xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="], + "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], + "xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="], + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], @@ -5545,6 +6126,8 @@ "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -5567,6 +6150,8 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], @@ -5671,7 +6256,15 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="], + + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.13", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-ZPtVYt5QIJzOta1kdUiDuCx4HhFkvNPv/rvmZ2b1iXwybYjJsCnNYR4PAw4kW7rgVfDARvHXcU64efWuqNp6bw=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="], + + "@alcalzone/ansi-tokenize/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -5685,6 +6278,8 @@ "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="], + "@astrojs/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "@astrojs/sitemap/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], @@ -5693,6 +6288,12 @@ "@astrojs/starlight/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@asyncapi/parser/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@asyncapi/parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="], + "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="], "@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="], @@ -5883,6 +6484,8 @@ "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "@jsx-email/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], @@ -5893,18 +6496,130 @@ "@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + "@kobalte/core/solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "@mdx-js/mdx/remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + "@mintlify/cli/chalk": ["chalk@5.2.0", "", {}, "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA=="], + + "@mintlify/cli/fs-extra": ["fs-extra@11.2.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw=="], + + "@mintlify/cli/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/cli/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "@mintlify/cli/openid-client": ["openid-client@6.8.2", "", { "dependencies": { "jose": "^6.1.3", "oauth4webapi": "^3.8.4" } }, "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA=="], + + "@mintlify/cli/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/cli/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "@mintlify/cli/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/cli/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/cli/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "@mintlify/common/acorn": ["acorn@8.11.2", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w=="], + + "@mintlify/common/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="], + + "@mintlify/common/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/common/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "@mintlify/common/mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="], + + "@mintlify/common/mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], + + "@mintlify/common/postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + + "@mintlify/common/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + + "@mintlify/common/remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="], + + "@mintlify/common/sucrase": ["sucrase@3.34.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "7.1.6", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw=="], + + "@mintlify/common/tailwindcss": ["tailwindcss@3.4.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og=="], + + "@mintlify/common/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/common/unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "@mintlify/link-rot/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/mdx/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], + + "@mintlify/mdx/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/mdx/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], + + "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/prebuild/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/prebuild/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/prebuild/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "@mintlify/previewing/chalk": ["chalk@5.2.0", "", {}, "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA=="], + + "@mintlify/previewing/chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="], + + "@mintlify/previewing/express": ["express@4.22.0", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ=="], + + "@mintlify/previewing/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/previewing/got": ["got@13.0.0", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA=="], + + "@mintlify/previewing/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/previewing/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/previewing/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/previewing/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/scraping/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="], + + "@mintlify/scraping/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/scraping/mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], + + "@mintlify/scraping/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + + "@mintlify/scraping/remark-mdx": ["remark-mdx@3.0.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA=="], + + "@mintlify/scraping/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/scraping/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/scraping/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], + + "@mintlify/validation/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/validation/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "@mintlify/validation/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "@mintlify/validation/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], + + "@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], "@modelcontextprotocol/sdk/hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="], "@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], - "@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], @@ -5973,10 +6688,14 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], "@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@opencode-ai/core/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], "@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], @@ -5987,6 +6706,8 @@ "@opencode-ai/llm/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], "@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], @@ -5999,10 +6720,16 @@ "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], + "@oxc-parser/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], "@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], @@ -6011,6 +6738,14 @@ "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], + "@puppeteer/browsers/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "@puppeteer/browsers/tar-fs": ["tar-fs@3.1.3", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ=="], + + "@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], @@ -6035,8 +6770,16 @@ "@shikijs/themes/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], + "@shikijs/twoslash/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@shikijs/twoslash/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + + "@slack/bolt/express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="], + "@slack/bolt/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + "@slack/bolt/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], "@slack/socket-mode/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], @@ -6053,6 +6796,8 @@ "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], + "@solid-primitives/memo/@solid-primitives/utils": ["@solid-primitives/utils@6.4.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg=="], + "@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], @@ -6063,9 +6808,29 @@ "@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="], - "@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + "@stoplight/better-ajv-errors/leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "@stoplight/json/jsonc-parser": ["jsonc-parser@2.2.1", "", {}, "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="], + + "@stoplight/json/safe-stable-stringify": ["safe-stable-stringify@1.1.1", "", {}, "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="], + + "@stoplight/json-ref-readers/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@stoplight/json-ref-resolver/immer": ["immer@9.0.21", "", {}, "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="], - "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "@stoplight/spectral-core/@stoplight/types": ["@stoplight/types@13.6.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ=="], + + "@stoplight/spectral-core/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@stoplight/spectral-core/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@stoplight/spectral-functions/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@stoplight/spectral-parsers/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="], + + "@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="], + + "@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], @@ -6091,6 +6856,14 @@ "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], + + "@vercel/cli-exec/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@vercel/functions/@vercel/oidc": ["@vercel/oidc@3.8.0", "", { "dependencies": { "@vercel/cli-config": "0.2.0", "@vercel/cli-exec": "1.0.0", "jose": "^5.9.6" } }, "sha512-r00laGW6Pv778RoR6M2NxX91ycSj+PBwVo+fOb9Bif+F0IyUKt25zrvBzfEzQpeAzbqOgPZyQibEWDdDFApd+A=="], + + "@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + "@vitest/coverage-v8/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], "@vitest/coverage-v8/magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], @@ -6103,9 +6876,7 @@ "@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], - "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="], @@ -6113,6 +6884,8 @@ "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], "ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], @@ -6141,6 +6914,8 @@ "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], + "astro/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "astro/common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], @@ -6161,9 +6936,11 @@ "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], - "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -6173,6 +6950,8 @@ "c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "chromium-bidi/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -6183,10 +6962,16 @@ "config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "cosmiconfig/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "cosmiconfig/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "dir-compare/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -6229,8 +7014,16 @@ "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "engine.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "engine.io/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "engine.io-client/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "esast-util-from-js/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -6243,22 +7036,20 @@ "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], + "favicons/xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], - "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + "gitlab-ai-provider/openai": ["openai@6.39.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A=="], "gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -6277,6 +7068,26 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "ink/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "ink/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "ink/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "ink-spinner/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "is-online/got": ["got@12.6.1", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ=="], + + "is-online/p-timeout": ["p-timeout@5.1.0", "", {}, "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew=="], + "istanbul-reports/html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -6285,16 +7096,20 @@ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "keytar/node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], - "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], + "micromark-extension-mdxjs/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "micromark-extension-mdxjs/micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], @@ -6311,6 +7126,10 @@ "motion/framer-motion": ["framer-motion@12.40.0", "", { "dependencies": { "motion-dom": "^12.40.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg=="], + "next-mdx-remote-client/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "next-mdx-remote-client/serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], + "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], "nitro/undici": ["undici@7.26.0", "", {}, "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg=="], @@ -6319,14 +7138,14 @@ "node-gyp/undici": ["undici@6.26.0", "", {}, "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A=="], - "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], "nypm/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + "opencode/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="], + "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6335,18 +7154,28 @@ "opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + "opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "oxc-parser/@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + + "p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "p-some/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse-json/json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -6367,6 +7196,8 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prebuild-install/node-abi": ["node-abi@3.94.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g=="], + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -6375,24 +7206,32 @@ "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "proxy-agent/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "public-ip/got": ["got@12.6.1", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ=="], + + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "react-reconciler/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], - "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], - "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "shiki/@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="], "shiki/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], @@ -6401,12 +7240,20 @@ "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + "socket.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], + + "solid-transition-size/@corvu/utils": ["@corvu/utils@0.3.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.7" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ZWlyWEE8qV9+CB9OAyo2bTrZGXQN9ZeM+JfYv89zoR+lRACKTDuoOZEdiyL8Uc7U5dUSH1uTqKhTTnaHWb+wZA=="], + "sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], "sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6419,6 +7266,12 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "terser/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], @@ -6433,24 +7286,30 @@ "tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="], - "type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + + "unbzip2-stream/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + "unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "unused-filename/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], "unzipper/fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], - "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg=="], + "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SoPSkrL5cbNQnAljRsJ7pOzJ2FmWgnhC0lfFOda873ycCdFJL1A+h3Ib7mX2spcv3XnNaO13y/45/0RyqNWlIQ=="], "venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.32", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Kwj499fTcN9bP/AfGoPU7JWIXeP6VZqKI6omsH062c9E2G4gdjeJczkz4z/tYSkzYjLE2AI3DtZbMfs6D7vn2Q=="], "verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + "vite-plugin-dynamic-import/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], "vitest/@vitest/expect": ["@vitest/expect@4.1.7", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w=="], @@ -6475,6 +7334,8 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], "yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], @@ -6531,6 +7392,8 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -6559,6 +7422,8 @@ "@astrojs/starlight/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -6641,6 +7506,10 @@ "@hey-api/json-schema-ref-parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], "@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], @@ -6703,27 +7572,159 @@ "@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "@mintlify/cli/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/cli/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/cli/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "@mintlify/cli/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "@mintlify/cli/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "@mintlify/cli/openid-client/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], + + "@mintlify/cli/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/cli/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/common/hast-util-to-html/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + + "@mintlify/common/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/common/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/mdast-util-mdx-jsx/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "@mintlify/common/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "@mintlify/common/sucrase/glob": ["glob@7.1.6", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="], + + "@mintlify/common/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + + "@mintlify/common/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "@mintlify/common/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + + "@mintlify/common/tailwindcss/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "@mintlify/common/tailwindcss/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "@mintlify/common/tailwindcss/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "@mintlify/common/tailwindcss/sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "@mintlify/link-rot/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@mintlify/link-rot/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "@mintlify/link-rot/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "@mintlify/mdx/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + + "@mintlify/mdx/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@mintlify/mdx/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@mintlify/mdx/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], + + "@mintlify/mdx/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], - "@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "@mintlify/mdx/shiki/@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], - "@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + "@mintlify/mdx/shiki/@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], - "@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "@mintlify/mdx/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], - "@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "@mintlify/prebuild/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - "@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "@mintlify/prebuild/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "@mintlify/prebuild/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - "@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "@mintlify/prebuild/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], - "@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "@mintlify/prebuild/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], - "@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "@mintlify/previewing/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "@modelcontextprotocol/sdk/express/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], + "@mintlify/previewing/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "@mintlify/previewing/express/body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="], + + "@mintlify/previewing/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + + "@mintlify/previewing/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "@mintlify/previewing/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + + "@mintlify/previewing/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "@mintlify/previewing/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + + "@mintlify/previewing/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + + "@mintlify/previewing/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + + "@mintlify/previewing/express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], + + "@mintlify/previewing/express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + + "@mintlify/previewing/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + + "@mintlify/previewing/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "@mintlify/previewing/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "@mintlify/previewing/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/previewing/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "@mintlify/previewing/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "@mintlify/previewing/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "@mintlify/previewing/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "@mintlify/previewing/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "@mintlify/previewing/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "@mintlify/previewing/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "@mintlify/previewing/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "@mintlify/previewing/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "@mintlify/previewing/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + + "@mintlify/previewing/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/previewing/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@mintlify/previewing/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "@mintlify/previewing/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "@mintlify/previewing/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/previewing/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/scraping/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/scraping/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/scraping/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/scraping/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/validation/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], @@ -6831,10 +7832,20 @@ "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], "@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + "@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -6847,6 +7858,34 @@ "@shikijs/stream/@shikijs/core/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], + "@slack/bolt/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "@slack/bolt/express/body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="], + + "@slack/bolt/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + + "@slack/bolt/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "@slack/bolt/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + + "@slack/bolt/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "@slack/bolt/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + + "@slack/bolt/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + + "@slack/bolt/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + + "@slack/bolt/express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], + + "@slack/bolt/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + + "@slack/bolt/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "@slack/bolt/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "@slack/bolt/raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -6869,16 +7908,34 @@ "@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + + "@storybook/csf-plugin/unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "@storybook/csf-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + "@vercel/cli-exec/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "@vercel/cli-exec/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@vercel/cli-exec/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@vercel/cli-exec/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@vercel/cli-exec/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "@vercel/cli-exec/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@vercel/cli-exec/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@vercel/functions/@vercel/oidc/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], @@ -6891,6 +7948,8 @@ "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6943,7 +8002,11 @@ "babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "better-opn/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "better-opn/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], "builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -6951,6 +8014,8 @@ "conf/dot-prop/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], + "cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], @@ -7001,13 +8066,13 @@ "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "engine.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "engine.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], - "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + "esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -7015,6 +8080,30 @@ "iconv-corefoundation/cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "inquirer/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "is-online/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "is-online/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "is-online/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "is-online/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "is-online/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "is-online/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "is-online/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "is-online/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "js-beautify/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -7033,24 +8122,60 @@ "motion/framer-motion/motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], + "next-mdx-remote-client/serialize-error/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], + "opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + "opencode/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="], + + "opencode/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="], + + "opencode/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-nJ0bAfegMAIJtrzMJtbzer1cS3nb7c7DsyU1S4nrPm7ZU0Mn6SBBZv5IGZZGTbpWTJwqKTSPeZJTXalbAxt1BA=="], + "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], + "prebuild-install/node-abi/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "public-ip/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "public-ip/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "public-ip/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "public-ip/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "public-ip/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "public-ip/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "public-ip/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "public-ip/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "public-ip/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "public-ip/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "socket.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "socket.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -7061,12 +8186,14 @@ "tw-to-css/tailwindcss/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], - "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "unzipper/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + "venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="], + + "venice-ai-sdk-provider/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="], + "venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -7203,6 +8330,10 @@ "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -7251,9 +8382,63 @@ "@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "@mintlify/cli/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/cli/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/cli/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/cli/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/common/remark-gfm/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/sucrase/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@mintlify/common/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "@mintlify/common/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "@mintlify/common/tailwindcss/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "@mintlify/previewing/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "@mintlify/previewing/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "@mintlify/previewing/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "@mintlify/previewing/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "@mintlify/previewing/express/body-parser/qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], + + "@mintlify/previewing/express/body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + + "@mintlify/previewing/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "@mintlify/previewing/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "@mintlify/previewing/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + + "@mintlify/previewing/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "@mintlify/previewing/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "@mintlify/previewing/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + + "@mintlify/previewing/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/previewing/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/previewing/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/previewing/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/scraping/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/scraping/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/scraping/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/scraping/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -7281,16 +8466,42 @@ "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@puppeteer/browsers/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "@slack/bolt/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "@slack/bolt/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "@slack/bolt/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "@slack/bolt/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "@slack/bolt/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "@slack/bolt/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + + "@slack/bolt/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@vercel/cli-exec/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -7349,6 +8560,8 @@ "electron-builder/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "engine.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "esbuild-plugin-copy/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -7357,22 +8570,34 @@ "iconv-corefoundation/cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "is-online/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "is-online/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + "js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "js-beautify/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "opencode/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], + "public-ip/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "public-ip/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + "socket.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -7413,8 +8638,36 @@ "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "@mintlify/cli/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/common/sucrase/glob/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + + "@mintlify/common/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "@mintlify/previewing/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@mintlify/previewing/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@mintlify/previewing/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/previewing/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/scraping/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/scraping/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@slack/bolt/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@slack/bolt/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], @@ -7449,6 +8702,8 @@ "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "@mintlify/common/sucrase/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], diff --git a/github/index.ts b/github/index.ts index 4e1af9cf55f4..e8acd9a9cef3 100644 --- a/github/index.ts +++ b/github/index.ts @@ -495,7 +495,6 @@ async function subscribeSessionEvents() { console.log("Subscribing to session events...") const TOOL: Record = { - todowrite: ["Todo", "\x1b[33m\x1b[1m"], bash: ["Bash", "\x1b[31m\x1b[1m"], edit: ["Edit", "\x1b[32m\x1b[1m"], glob: ["Glob", "\x1b[34m\x1b[1m"], diff --git a/infra/lake.ts b/infra/lake.ts index dd11ca38f977..d4e46168e366 100644 --- a/infra/lake.ts +++ b/infra/lake.ts @@ -67,6 +67,10 @@ const athenaWorkgroup = new aws.athena.Workgroup("LakeAthenaWorkgroup", { configuration: { enforceWorkgroupConfiguration: true, publishCloudwatchMetricsEnabled: true, + // Athena bills $5/TB scanned; kill any query that would scan more than 2 TB + // so a regression cannot silently burn money. Stats sync full passes scan + // ~250 GB as of 2026-07. + bytesScannedCutoffPerQuery: 2 * 1024 ** 4, resultConfiguration: { outputLocation: $interpolate`s3://${athenaResultsBucket.bucket}/`, }, diff --git a/infra/stats.ts b/infra/stats.ts index b5b0e1c600e5..10d37119f0d7 100644 --- a/infra/stats.ts +++ b/infra/stats.ts @@ -185,7 +185,9 @@ export const statSync = new sst.aws.Service("StatsSyncService", { cluster: lakeCluster, architecture: "arm64", cpu: "0.25 vCPU", - memory: "0.5 GB", + // 0.5 GB caused an OOM crash loop: every restart immediately re-ran the 4 Athena + // stats queries (~$5/pass) every ~5 minutes instead of hourly. + memory: "2 GB", image: { context: ".", dockerfile: "packages/stats/server/Dockerfile", diff --git a/nix/hashes.json b/nix/hashes.json index 3b57bf2269d5..50208fdbbeb7 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-OiWvZ57vuyHwiIKNtW1n1KX+MLmOXVG3x4fLKvUoGQw=", - "aarch64-linux": "sha256-RnPLxVEg/UsL5IeIFWmXMSLUOG6rVrajYxhyDYj1vTA=", - "aarch64-darwin": "sha256-KPIgcBA0pTFBPrCTSZgIbvEorbtWcMgXvyX9bFAypVs=", - "x86_64-darwin": "sha256-6jVU7/uVId0VD24MVQ8s8Ill5b6PsKdlBgHg+oceKRg=" + "x86_64-linux": "sha256-JTtn+wXTXg+yklvIMDLcGFaYhTU6ZrCgKT9JTNEQ3gA=", + "aarch64-linux": "sha256-gXU6zyhvAZrZirkL/PlHdkHtEof/7PVSPCaE34Jnd4U=", + "aarch64-darwin": "sha256-Q0oTG3uzOlD/X2kJingLle529lKFoTpyCW2rHXOZ6iE=", + "x86_64-darwin": "sha256-LINvKHxPibTlJeNzfACQx0x+Yj5oROT6Du3I5AtqqXk=" } } diff --git a/package.json b/package.json index 49507128d60c..b6329f9c9622 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,29 @@ "$schema": "https://json.schemastore.org/package.json", "name": "opencode", "description": "AI-powered development tool", + "version": "0.0.0", "private": true, "type": "module", "packageManager": "bun@1.3.14", "scripts": { - "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", + "dev": "bun run --cwd packages/cli --conditions=browser src/index.ts", "dev:desktop": "bun --cwd packages/desktop dev", "dev:web": "bun --cwd packages/app dev", "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", "dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev", "dev:storybook": "bun --cwd packages/storybook storybook", "lint": "oxlint", - "typecheck": "bun turbo typecheck", + "lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/core/src packages/server/src packages/protocol/src packages/cli/src", + "test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml", + "typecheck": "bun turbo typecheck --concurrency=3", + "typecheck:profile": "bun script/profile-typecheck.ts", + "typecheck:profile:packages": "bun script/profile-typecheck-packages.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "postinstall": "bun run --cwd packages/core fix-node-pty", "prepare": "husky", "random": "echo 'Random script'", "sso": "aws sso login --sso-session=opencode --no-browser", + "translate:app": "bun run script/translate-app.ts", "test": "echo 'do not run tests from root' && exit 1" }, "workspaces": { @@ -39,13 +45,14 @@ "@octokit/rest": "22.0.0", "@hono/standard-validator": "0.2.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.3.4", - "@opentui/keymap": "0.3.4", - "@opentui/solid": "0.3.4", + "@opentui/core": "0.4.3", + "@opentui/keymap": "0.4.3", + "@opentui/solid": "0.4.3", "@tanstack/solid-virtual": "3.13.28", "@shikijs/stream": "4.2.0", "ulid": "3.0.1", "@kobalte/core": "0.13.11", + "@corvu/drawer": "0.2.4", "@types/luxon": "3.7.1", "@types/node": "24.12.2", "@types/semver": "7.7.1", @@ -94,6 +101,7 @@ }, "devDependencies": { "@actions/artifact": "5.0.1", + "@ast-grep/cli": "0.44.0", "@tsconfig/bun": "catalog:", "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", @@ -104,7 +112,7 @@ "prettier": "3.6.2", "semver": "^7.6.0", "sst": "catalog:", - "turbo": "2.8.13" + "turbo": "2.10.2" }, "dependencies": { "@aws-sdk/client-s3": "3.933.0", @@ -146,13 +154,14 @@ "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", - "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch" + "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", + "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch" } } diff --git a/packages/app/e2e/performance/timeline-stability/README.md b/packages/app/e2e/performance/timeline-stability/README.md new file mode 100644 index 000000000000..3e9def275730 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/README.md @@ -0,0 +1,61 @@ +# Timeline Layout Continuity + +Run from `packages/app`: + +```sh +bun run test:stability +``` + +The suite runs a production build in one Chromium worker. Selected scenarios use deterministic 4x CPU stress after application readiness. This is a stress profile, not emulation of a specific device. + +## What It Proves + +The continuity probe samples DOM-derived layout and visibility state across browser render opportunities. Tests declare explicit contracts such as: + +- Preserve a visible semantic anchor while the user is away from the bottom. +- Preserve end anchoring while active content grows or new content appears. +- Keep adjacent visible rows ordered without material overlap. +- Keep user-selected disclosure state through updates and virtualization. +- Avoid a sampled blank interval while one visible surface replaces another. +- Preserve logical row and control identity where local state or focus depends on it. +- Keep keyboard, wheel, and nested-scroll ownership consistent during remeasurement. + +The suite exercises real browser reducer, projection, component, virtualizer, layout, focus, and interaction code. The backend and event producer are controlled fixtures. + +## What It Does Not Prove + +The pass/fail oracle does not inspect every compositor-presented pixel. A sample taken after `requestAnimationFrame` is a DOM/layout observation, not proof that every sampled state was displayed or that every displayed frame was sampled. + +The suite does not provide complete coverage for: + +- Compositor-only or raster-only glitches. +- Color, contrast, canvas, WebGL, masks, irregular clips, or arbitrary occlusion. +- Physical display refresh rates, native OS scaling, or a named low-end device. +- TCP packetization, proxy buffering, or the complete real server/provider pipeline. + +Playwright video, trace, screenshots, and observation JSON are diagnostic evidence. They are not pixel baselines and do not participate in normal pass/fail decisions. + +For optional before/violation/after screenshots, set `OPENCODE_STABILITY_CAPTURE=1`. Capture is opt-in because compositor readback can perturb timing. + +## Test Layers + +- **Projection:** admitted rows, grouping, labels, and final visible states. +- **Local state:** disclosure state, identity, duplicate delivery, and virtualization restoration. +- **Interaction:** wheel, keyboard, nested scrolling, actionability, and focus behavior. +- **Layout continuity:** anchoring, adjacency, responsive reflow, and visible surface handoffs. +- **Reducer hardening:** validly shaped but intentionally reordered, duplicated, removed, or replaced events. +- **Oracle contract:** pure analyzer and browser sampler calibration tests. + +Production-lifecycle fixtures should model states emitted by the current producer. Impossible or reordered sequences belong in reducer-hardening tests and must not be described as normal provider behavior. + +## Diagnostics + +Failures retain: + +- `video.webm` +- `trace.zip` +- failure screenshot +- sampled DOM/layout trace JSON +- event markers and summarized violations + +The analyzer records both unclipped layout bounds and ancestor-clipped visible intersections. Scrollbar and raw `scrollTop` changes alone do not fail continuity checks; user-visible semantic anchor movement does. diff --git a/packages/app/e2e/performance/timeline-stability/adverse.spec.ts b/packages/app/e2e/performance/timeline-stability/adverse.spec.ts new file mode 100644 index 000000000000..7ce1be906459 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/adverse.spec.ts @@ -0,0 +1,250 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + shell, + textPart, + toolPart, + userMessage, + waitForVisualSettle, + type TimelineMessage, +} from "./fixture" + +test.describe("timeline adverse visual stability", () => { + test("does not pull a scrolled-away user while an active shell grows", async ({ page }, testInfo) => { + const activeShellID = "prt_adverse_01_shell" + const messages = [ + ...history(24), + userMessage(), + assistantMessage([shell(activeShellID, "running")], { completed: false }), + ] + const timeline = await setupTimeline(page, { + messages, + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + eventRetry: 30, + }) + const scroller = page.locator(".scroll-view__viewport", { + has: page.locator('[data-timeline-row="AssistantPart"]'), + }) + await scroller.evaluate((element) => { + element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: -450 })) + element.scrollTop = Math.max(0, element.scrollHeight - element.clientHeight - 450) + }) + await page.waitForTimeout(150) + await expect + .poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) + .toBeGreaterThan(100) + const anchor = await scroller.evaluate((element) => { + const view = element.getBoundingClientRect() + return [...element.querySelectorAll("[data-timeline-key]")].find((row) => { + const rect = row.getBoundingClientRect() + return rect.top >= view.top + 40 && rect.bottom <= view.bottom - 40 + })?.dataset.timelineKey + }) + expect(anchor).toBeTruthy() + await waitForVisualSettle(page, [`[data-timeline-key="${anchor}"]`]) + + const regions = defineVisualRegions({ + anchor: { selector: `[data-timeline-key="${anchor}"]` }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(activeShellID, "running", lines(1))), 180) + await timeline.send(partUpdated(shell(activeShellID, "running", lines(10))), 90) + await timeline.send(partUpdated(shell(activeShellID, "running", lines(50))), 350) + await timeline.send(partUpdated(shell(activeShellID, "completed", lines(50))), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "scrolled-away-shell", + trace, + visualPlan(regions, [ + { type: "required", regions: ["anchor"] }, + { type: "unique", regions: ["anchor"] }, + { type: "stable", regions: ["anchor"] }, + { type: "fixed", regions: ["anchor"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + ]), + ) + }) + + test("preserves an explicit shell state across virtualization", async ({ page }) => { + const targetID = "prt_virtual_shell" + const messages = [ + userMessage(undefined, { id: "msg_0000_virtual_user", created: 1700000000000 }), + assistantMessage([shell(targetID, "completed", lines(20))], { + id: "msg_0001_virtual_assistant", + parentID: "msg_0000_virtual_user", + created: 1700000001000, + }), + ...history(35, 10), + ] + await setupTimeline(page, { messages, settings: { shellToolPartsExpanded: false } }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate((element) => { + element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: -1_000 })) + element.scrollTop = 0 + }) + await page.waitForTimeout(300) + const trigger = page.locator(`[data-timeline-part-id="${targetID}"] [data-slot="collapsible-trigger"]`) + await expect(trigger).toBeVisible() + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + + await scroller.evaluate((element) => (element.scrollTop = element.scrollHeight)) + await expect(page.locator(`[data-timeline-part-id="${targetID}"]`)).toHaveCount(0) + await scroller.evaluate((element) => (element.scrollTop = 0)) + await expect(trigger).toBeVisible() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + }) + + test("keeps narrow viewport rows ordered during long shell growth", async ({ page }, testInfo) => { + const shellID = "prt_narrow_01_shell" + const followingID = "prt_narrow_02_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [shell(shellID, "running"), textPart(followingID, "A narrow following row that wraps across lines.")], + { + completed: false, + }, + ), + ], + settings: { shellToolPartsExpanded: true }, + viewport: { width: 430, height: 800 }, + cpuRate: 4, + }) + await waitForVisualSettle(page, [ + `[data-timeline-part-id="${shellID}"]`, + `[data-timeline-part-id="${followingID}"]`, + ]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "running", wideLines(10))), 100) + await timeline.send(partUpdated(shell(shellID, "running", wideLines(50))), 300) + await timeline.send(partUpdated(shell(shellID, "completed", wideLines(50))), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "narrow-shell", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ), + ) + }) + + test("keeps visible rows ordered while resizing desktop to narrow and back", async ({ page }, testInfo) => { + const shellID = "prt_resize_01_shell" + const contextIDs = ["prt_resize_02_read", "prt_resize_03_glob"] + const followingID = "prt_resize_04_following" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + shell(shellID, "completed", wideLines(15)), + toolPart(contextIDs[0]!, "read", "completed", { filePath: "src/a.ts" }), + toolPart(contextIDs[1]!, "glob", "completed", { path: ".", pattern: "**/*.ts" }), + textPart(followingID, "Following responsive timeline content that wraps on narrow screens."), + ]), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + seedHistory: true, + }) + const group = `[data-timeline-part-ids="${contextIDs.join(",")}"]` + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + context: { selector: group, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await page.setViewportSize({ width: 430, height: 800 }) + await page.waitForTimeout(500) + await page.setViewportSize({ width: 900, height: 800 }) + await page.waitForTimeout(500) + await page.setViewportSize({ width: 1400, height: 900 }) + await page.waitForTimeout(500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "responsive-resize", + trace, + visualPlan(regions, [ + { type: "required", regions: ["shell", "context", "following"] }, + { type: "unique", regions: ["shell", "context", "following"] }, + { type: "stable", regions: ["shell", "context", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 4, maxReversals: 4 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["shell", "context", "following"] }, + ]), + ) + }) +}) + +function history(count: number, offset = 0): TimelineMessage[] { + return Array.from({ length: count }, (_, index) => { + const value = index + offset + const prefix = `msg_0${String(value).padStart(3, "0")}_history` + const userID = `${prefix}_a_user` + return [ + userMessage(undefined, { id: userID, created: 1699990000000 + value * 10_000 }), + assistantMessage( + [ + textPart( + `prt_history_${String(value).padStart(3, "0")}`, + `Historical response ${value}. ${"Stable history content. ".repeat(8)}`, + ), + ], + { + id: `${prefix}_b_assistant`, + parentID: userID, + created: 1699990001000 + value * 10_000, + }, + ), + ] + }).flat() +} + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} + +function wideLines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1} ${"wide-output-".repeat(20)}`).join("\n") +} diff --git a/packages/app/e2e/performance/timeline-stability/context-matrix.spec.ts b/packages/app/e2e/performance/timeline-stability/context-matrix.spec.ts new file mode 100644 index 000000000000..b3438b77c06b --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/context-matrix.spec.ts @@ -0,0 +1,192 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantID, + assistantMessage, + event, + partUpdated, + setupTimeline, + textPart, + toolPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +const inputs = { + read: { filePath: "src/a.ts", offset: 0, limit: 120 }, + glob: { path: ".", pattern: "**/*.ts" }, + grep: { path: ".", pattern: "stable", include: "*.ts" }, + list: { path: "src" }, +} + +test("appends context operations while the group is expanded", async ({ page }, testInfo) => { + const firstID = "prt_append_01_read" + const followingID = "prt_append_99_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([toolPart(firstID, "read", "running", inputs.read), textPart(followingID, "Following append")], { + completed: false, + }), + ], + cpuRate: 4, + }) + const initialGroup = `[data-timeline-part-ids="${firstID}"]` + await page.locator(`${initialGroup} [data-slot="collapsible-trigger"]`).click() + await waitForVisualSettle(page, [initialGroup, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + context: { + selector: '[data-timeline-part-ids^="prt_append_01_read"]', + closest: '[data-timeline-row="AssistantPart"]', + }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(toolPart("prt_append_02_glob", "glob", "running", inputs.glob)), 180) + await timeline.send(partUpdated(toolPart("prt_append_03_grep", "grep", "completed", inputs.grep)), 240) + await timeline.send(partUpdated(toolPart("prt_append_04_list", "list", "completed", inputs.list)), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "context-append", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["context", "following"] }, + { type: "unique", regions: ["context", "following"] }, + { type: "stable", regions: ["context", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: ["following"], maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["context", "following"] }, + ], + { perMarker: true }, + ), + ) + await expect( + page.locator( + '[data-timeline-part-ids="prt_append_01_read,prt_append_02_glob,prt_append_03_grep,prt_append_04_list"]', + ), + ).toBeVisible() + await expect( + page.locator('[data-timeline-part-ids^="prt_append_01_read"] [data-slot="collapsible-trigger"]'), + ).toHaveAttribute("aria-expanded", "true") +}) + +test("splits and merges context groups when a middle text part changes", async ({ page }, testInfo) => { + const textID = "prt_split_02_text" + const followingID = "prt_split_99_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart("prt_split_01_read", "read", "completed", inputs.read), + textPart(textID, "Boundary"), + toolPart("prt_split_03_glob", "glob", "completed", inputs.glob), + textPart(followingID, "Following split groups"), + ]), + ], + cpuRate: 4, + }) + const regions = defineVisualRegions({ + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + event("message.part.removed", { sessionID: "ses_timeline_stability", messageID: assistantID, partID: textID }), + 500, + ) + await expect(page.locator('[data-timeline-part-ids="prt_split_01_read,prt_split_03_glob"]')).toBeVisible() + await timeline.send(partUpdated(textPart(textID, "Boundary restored")), 500) + await expect(page.locator('[data-timeline-part-ids="prt_split_01_read"]')).toBeVisible() + await expect(page.locator('[data-timeline-part-ids="prt_split_03_glob"]')).toBeVisible() + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "context-split-merge", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["following"] }, + { type: "unique", regions: ["following"] }, + { type: "stable", regions: ["following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 1 }, + { type: "label-stability", regions: "all" }, + ], + { perMarker: true }, + ), + ) +}) + +test("removing the first context member replaces the group once without overlapping following content", async ({ + page, +}, testInfo) => { + const ids = ["prt_key_01_read", "prt_key_02_glob", "prt_key_03_grep"] + const followingID = "prt_key_99_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(ids[0]!, "read", "completed", inputs.read), + toolPart(ids[1]!, "glob", "completed", inputs.glob), + toolPart(ids[2]!, "grep", "completed", inputs.grep), + textPart(followingID, "Following replaced group"), + ]), + ], + cpuRate: 4, + }) + const original = page.locator(`[data-timeline-part-ids="${ids.join(",")}"]`) + const originalRowKey = await original.evaluate((element) => + element.closest("[data-timeline-key]")?.getAttribute("data-timeline-key"), + ) + await original.locator('[data-slot="collapsible-trigger"]').click() + await expect(original.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true") + const regions = defineVisualRegions({ + context: { + selector: '[data-timeline-part-ids*="prt_key_02_glob"]', + closest: '[data-timeline-row="AssistantPart"]', + }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + event("message.part.removed", { sessionID: "ses_timeline_stability", messageID: assistantID, partID: ids[0] }), + 500, + ) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "context-first-remove", + trace, + visualPlan(regions, [ + { type: "required", regions: ["context", "following"] }, + { type: "unique", regions: ["context", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["context", "following"] }, + ]), + ) + await expect(page.locator(`[data-timeline-part-ids="${ids.slice(1).join(",")}"]`)).toBeVisible() + expect( + await page + .locator(`[data-timeline-part-ids="${ids.slice(1).join(",")}"]`) + .evaluate((element) => element.closest("[data-timeline-key]")?.getAttribute("data-timeline-key")), + ).toBe(originalRowKey) + await expect( + page.locator(`[data-timeline-part-ids="${ids.slice(1).join(",")}"] [data-slot="collapsible-trigger"]`), + ).toHaveAttribute("aria-expanded", "true") +}) diff --git a/packages/app/e2e/performance/timeline-stability/environment-matrix.spec.ts b/packages/app/e2e/performance/timeline-stability/environment-matrix.spec.ts new file mode 100644 index 000000000000..3455438a7365 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/environment-matrix.spec.ts @@ -0,0 +1,113 @@ +import { test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + shell, + textPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +// Fractional scaling exercises different browser rounding than the baseline. +for (const deviceScaleFactor of [1, 1.25]) { + test(`keeps shell growth ordered at device scale ${deviceScaleFactor}`, async ({ page }, testInfo) => { + const shellID = `prt_dpr_${String(deviceScaleFactor).replace(".", "_")}_01_shell` + const followingID = `prt_dpr_${String(deviceScaleFactor).replace(".", "_")}_02_following` + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following scaled shell")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + deviceScaleFactor, + seedHistory: true, + }) + await waitForVisualSettle(page, [ + `[data-timeline-part-id="${shellID}"]`, + `[data-timeline-part-id="${followingID}"]`, + ]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "running", lines(20))), 180) + await timeline.send(partUpdated(shell(shellID, "completed", lines(20))), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, `dpr-${deviceScaleFactor}`, trace, shellPlan(regions)) + }) +} + +for (const reducedMotion of [true]) { + test(`keeps shell and status transitions ordered with reduced motion ${reducedMotion}`, async ({ + page, + }, testInfo) => { + const shellID = `prt_motion_${reducedMotion}_01_shell` + const followingID = `prt_motion_${reducedMotion}_02_following` + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following motion profile")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + reducedMotion, + cpuRate: 4, + seedHistory: true, + }) + await waitForVisualSettle(page, [ + `[data-timeline-part-id="${shellID}"]`, + `[data-timeline-part-id="${followingID}"]`, + ]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "completed", lines(10))), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, `reduced-motion-${reducedMotion}`, trace, shellPlan(regions)) + }) +} + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} + +function shellPlan>( + regions: Regions & Record<"shell" | "following", { selector: string }>, +) { + return visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ) +} diff --git a/packages/app/e2e/performance/timeline-stability/file-matrix.spec.ts b/packages/app/e2e/performance/timeline-stability/file-matrix.spec.ts new file mode 100644 index 000000000000..0690f02706fc --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/file-matrix.spec.ts @@ -0,0 +1,121 @@ +import { test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + textPart, + toolPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +const profiles = [ + { name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } }, + { + name: "multi patch", + tool: "patch", + input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] }, + }, +] as const + +for (const profile of profiles) { + test(`stabilizes ${profile.name} pending to completed`, async ({ page }, testInfo) => { + const partID = `prt_file_matrix_${profiles.indexOf(profile)}` + const followingID = `prt_file_matrix_following_${profiles.indexOf(profile)}` + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [ + toolPart(partID, profile.tool, "pending", profile.input), + textPart(followingID, `Following ${profile.name}`), + ], + { completed: false }, + ), + ], + settings: { editToolPartsExpanded: true }, + cpuRate: 4, + }) + await waitForVisualSettle(page, [`[data-timeline-part-id="${partID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + tool: { selector: `[data-timeline-part-id="${partID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(toolPart(partID, profile.tool, "running", profile.input)), 180) + await timeline.send(partUpdated(completedPart(partID, profile)), 900) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + `file-${profile.name}`, + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["tool", "following"] }, + { type: "unique", regions: ["tool", "following"] }, + { type: "stable", regions: ["tool", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0, maxReversals: 1 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["tool", "following"] }, + ], + { perMarker: true }, + ), + ) + }) +} + +function completedPart(partID: string, profile: (typeof profiles)[number]) { + if (profile.tool === "edit") { + return toolPart(partID, profile.tool, "completed", profile.input, { + metadata: { + filediff: { + file: "src/edit.ts", + additions: 50, + deletions: 50, + before: source(50, false), + after: source(50, true), + }, + }, + }) + } + const files = [ + patchFile("src/a.ts", "update"), + patchFile("src/b.ts", "add"), + patchFile("src/old.ts", "delete"), + { ...patchFile("src/moved.ts", "move"), move: "src/new-place.ts" }, + ] + return toolPart(partID, profile.tool, "completed", profile.input, { metadata: { files } }) +} + +function patchFile(filePath: string, type: "add" | "update" | "delete" | "move") { + return { + filePath, + relativePath: filePath, + type, + additions: type === "delete" ? 0 : 20, + deletions: type === "add" ? 0 : 20, + before: type === "add" ? undefined : source(20, false), + after: type === "delete" ? undefined : source(20, true), + } +} + +function source(count: number, changed: boolean) { + return Array.from( + { length: count }, + (_, index) => `export const value${index} = ${changed ? index + 1 : index}\n`, + ).join("") +} diff --git a/packages/app/e2e/performance/timeline-stability/file-mutation.spec.ts b/packages/app/e2e/performance/timeline-stability/file-mutation.spec.ts new file mode 100644 index 000000000000..f411339adbec --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/file-mutation.spec.ts @@ -0,0 +1,113 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + textPart, + toolPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +test("adds patch files incrementally without resetting outer expansion", async ({ page }, testInfo) => { + const patchID = "prt_incremental_01_patch" + const followingID = "prt_incremental_02_following" + const first = patchFile("src/a.ts", "update") + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [ + toolPart(patchID, "patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }), + textPart(followingID, "Following incremental patch"), + ], + { completed: false }, + ), + ], + settings: { editToolPartsExpanded: true }, + cpuRate: 4, + seedHistory: true, + }) + const trigger = page.locator(`[data-timeline-part-id="${patchID}"] [data-slot="collapsible-trigger"]`).first() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await waitForVisualSettle(page, [`[data-timeline-part-id="${patchID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + patch: { selector: `[data-timeline-part-id="${patchID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + const second = patchFile("src/b.ts", "add") + const third = patchFile("src/old.ts", "delete") + await timeline.send( + partUpdated( + toolPart( + patchID, + "patch", + "running", + { files: [first.filePath, second.filePath] }, + { metadata: { files: [first, second] } }, + ), + ), + 240, + ) + await timeline.send( + partUpdated( + toolPart( + patchID, + "patch", + "completed", + { files: [first.filePath, second.filePath, third.filePath] }, + { metadata: { files: [first, second, third] } }, + ), + ), + 800, + ) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "incremental-patch", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["patch", "following"] }, + { type: "unique", regions: ["patch", "following"] }, + { type: "stable", regions: ["patch", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: ["following"], maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["patch", "following"] }, + ], + { perMarker: true }, + ), + ) + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await expect(page.locator('[data-scope="apply-patch"] [data-type="delete"]')).toBeVisible() +}) + +function patchFile(filePath: string, type: "add" | "update" | "delete") { + return { + filePath, + relativePath: filePath, + type, + additions: type === "delete" ? 0 : 4, + deletions: type === "add" ? 0 : 3, + before: type === "add" ? undefined : source(false), + after: type === "delete" ? undefined : source(true), + } +} + +function source(changed: boolean) { + return Array.from({ length: 12 }, (_, index) => `export const value${index} = ${changed ? index + 1 : index}\n`).join( + "", + ) +} diff --git a/packages/app/e2e/performance/timeline-stability/fixture.test.ts b/packages/app/e2e/performance/timeline-stability/fixture.test.ts new file mode 100644 index 000000000000..b003645e640c --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/fixture.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, test } from "bun:test" +import { + assistantMessage, + event, + toolPart, + userMessage, + validateTimelineEvent, + validateTimelineMessages, + type PartSeed, +} from "./fixture" + +describe("timeline fixture validation", () => { + test("accepts a valid timeline", () => { + expect(validateTimelineMessages([userMessage(), assistantMessage()])).toHaveLength(2) + }) + + test("rejects malformed SDK values at runtime", () => { + expect(() => + assistantMessage([], { + error: { name: "APIError", data: { message: "failed" } } as never, + }), + ).toThrow() + expect(() => + validateTimelineEvent({ + directory: "C:/OpenCode/TimelineStability", + payload: { + id: "evt_invalid_status", + type: "session.status", + properties: { sessionID: "ses_timeline_stability", status: { type: "retry", attempt: 1 } }, + }, + }), + ).toThrow() + }) + + test("rejects duplicate IDs and orphan assistants", () => { + expect(() => validateTimelineMessages([userMessage(), userMessage()])).toThrow(/duplicate message ID/) + expect(() => + validateTimelineMessages([userMessage(), assistantMessage([], { parentID: "msg_missing_parent" })]), + ).toThrow(/parent user/) + }) + + test("assigns deterministic event IDs", () => { + const first = event("session.status", { sessionID: "ses_timeline_stability", status: { type: "busy" } }) + const second = event("session.status", { sessionID: "ses_timeline_stability", status: { type: "idle" } }) + expect(first.payload.id).toMatch(/^evt_timeline_\d{4}$/) + expect(Number(second.payload.id.slice(-4))).toBe(Number(first.payload.id.slice(-4)) + 1) + }) +}) + +if (false) { + const userSeed = { id: "prt_type_user", type: "text", text: "typed" } satisfies PartSeed<"user"> + userMessage([userSeed]) + + // @ts-expect-error Tool completion fields are not valid while pending. + toolPart("prt_invalid_pending", "bash", "pending", {}, { output: "impossible" }) + // @ts-expect-error Tool completion fields are not valid while running. + toolPart("prt_invalid_running", "bash", "running", {}, { output: "impossible" }) + // @ts-expect-error Tool error fields are not valid after completion. + toolPart("prt_invalid_completed", "bash", "completed", {}, { error: "impossible" }) + + assistantMessage([ + // @ts-expect-error Agent references belong to user messages, not assistant messages. + { id: "prt_invalid_owner", type: "agent", name: "explore", source: { value: "@explore", start: 0, end: 8 } }, + ]) + + // @ts-expect-error Retry status events require message and next. + event("session.status", { sessionID: "ses_timeline_stability", status: { type: "retry", attempt: 1 } }) +} diff --git a/packages/app/e2e/performance/timeline-stability/fixture.ts b/packages/app/e2e/performance/timeline-stability/fixture.ts new file mode 100644 index 000000000000..e63445e87d68 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/fixture.ts @@ -0,0 +1,561 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { Event } from "@opencode-ai/schema/event" +import { SessionStatusEvent } from "@opencode-ai/schema/session-status-event" +import { SessionV1 } from "@opencode-ai/schema/session-v1" +import type { + AssistantMessage, + GlobalEvent, + Message, + Part, + Session, + SessionStatus, + ToolPart, + ToolState, + UserMessage, +} from "@opencode-ai/sdk/v2/client" +import { expect, type Page } from "@playwright/test" +import { Schema } from "effect" +import { mockOpenCodeServer } from "../../utils/mock-server" +import { installSseTransport } from "../../utils/sse-transport" +import { expectSessionTitle } from "../../utils/waits" + +export const directory = "C:/OpenCode/TimelineStability" +export const projectID = "proj_timeline_stability" +export const sessionID = "ses_timeline_stability" +export const userID = "msg_1000_timeline_user" +export const assistantID = "msg_1001_timeline_assistant" +export const title = "Timeline visual stability" +export const model = { providerID: "opencode", modelID: "claude-opus-4-6", variant: "max" } + +type TimelinePayload = Extract< + GlobalEvent["payload"], + { + type: + | "message.updated" + | "message.removed" + | "message.part.updated" + | "message.part.removed" + | "message.part.delta" + | "session.status" + } +> + +type DeepReadonly = Value extends readonly unknown[] + ? { readonly [Key in keyof Value]: DeepReadonly } + : Value extends object + ? { readonly [Key in keyof Value]: DeepReadonly } + : Value + +export type TimelineEvent = DeepReadonly & { payload: TimelinePayload }> +export type EventPayload = TimelineEvent +export type ToolStatus = ToolState["status"] +export type TimelineMessage = { info: UserMessage; parts: Part[] } | { info: AssistantMessage; parts: Part[] } + +type UserPart = Extract +type AssistantPart = Exclude +type OwnedPart = Owner extends "user" ? UserPart : AssistantPart +export type PartSeed = + OwnedPart extends infer Candidate + ? Candidate extends Part + ? Omit + : never + : never + +type ToolOptions = State extends "pending" + ? { output?: never; title?: never; metadata?: never; error?: never } + : State extends "running" + ? { title?: string; metadata?: Record; output?: never; error?: never } + : State extends "error" + ? { error?: string; metadata?: Record; output?: never; title?: never } + : { output?: string; title?: string; metadata?: Record; error?: never } + +const decodeOptions = { errors: "all", onExcessProperty: "error" } as const +const decodeMessage = Schema.decodeUnknownSync(SessionV1.WithParts) +const decodePart = Schema.decodeUnknownSync(SessionV1.Part) +const decodeStatus = Schema.decodeUnknownSync(SessionStatusEvent.Info) +const timelineEventSchema = Schema.Union([ + eventSchema("message.updated", SessionV1.Event.MessageUpdated.data), + eventSchema("message.removed", SessionV1.Event.MessageRemoved.data), + eventSchema("message.part.updated", SessionV1.Event.PartUpdated.data), + eventSchema("message.part.removed", SessionV1.Event.PartRemoved.data), + eventSchema("message.part.delta", SessionV1.Event.PartDelta.data), + eventSchema("session.status", SessionStatusEvent.Status.data), +]) +const decodeEvent = Schema.decodeUnknownSync(timelineEventSchema) +let eventSequence = 0 + +export async function setupTimeline( + page: Page, + input: { + messages?: TimelineMessage[] + settings?: Record + sessions?: Session[] + cpuRate?: number + viewport?: { width: number; height: number } + eventRetry?: number + reducedMotion?: boolean + locale?: string + deviceScaleFactor?: number + seedHistory?: boolean + } = {}, +) { + const sessions = input.sessions ?? [session()] + const messages = validateTimelineMessages([ + ...(input.seedHistory ? historyMessages(18) : []), + ...(input.messages ?? [userMessage(), assistantMessage()]), + ]) + const active = messages.findLast((message) => message.info.role === "assistant") + const initialStatus = decodeStatus( + active?.info.role === "assistant" && active.info.time.completed === undefined ? { type: "busy" } : { type: "idle" }, + decodeOptions, + ) + const transport = await installSseTransport(page, { + server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + retry: input.eventRetry ?? 20, + }) + await mockOpenCodeServer(page, { + directory, + project: project(), + provider: provider(), + sessions, + sessionStatus: { [sessionID]: initialStatus }, + pageMessages: () => ({ + items: messages, + }), + }) + await page.addInitScript((settings) => { + localStorage.setItem( + "settings.v3", + JSON.stringify({ + general: { + editToolPartsExpanded: false, + shellToolPartsExpanded: false, + showReasoningSummaries: false, + showSessionProgressBar: true, + ...settings, + }, + }), + ) + }, input.settings ?? {}) + if (input.locale) { + await page.addInitScript((locale) => { + localStorage.setItem("opencode.global.dat:language", JSON.stringify({ locale })) + }, input.locale) + } + if (input.reducedMotion) await page.emulateMedia({ reducedMotion: "reduce" }) + await page.setViewportSize(input.viewport ?? { width: 1400, height: 900 }) + if (input.deviceScaleFactor) { + const devtools = await page.context().newCDPSession(page) + const viewport = input.viewport ?? { width: 1400, height: 900 } + await devtools.send("Emulation.setDeviceMetricsOverride", { + width: viewport.width, + height: viewport.height, + deviceScaleFactor: input.deviceScaleFactor, + mobile: false, + }) + } + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await transport.waitForConnection() + await expectSessionTitle(page, title) + if (input.cpuRate && input.cpuRate > 1) { + const devtools = await page.context().newCDPSession(page) + await devtools.send("Emulation.setCPUThrottlingRate", { rate: input.cpuRate }) + } + + return { + transport, + async send(event: TimelineEvent, delay = 0) { + const valid = validateTimelineEvent(event) + await transport.send(valid, { marker: describeEvent(valid) }) + if (delay) await page.waitForTimeout(delay) + }, + async sendAll(sequence: { event: TimelineEvent; delay: number }[]) { + for (const item of sequence) { + const valid = validateTimelineEvent(item.event) + await transport.send(valid, { marker: describeEvent(valid) }) + await page.waitForTimeout(item.delay) + } + }, + async settle(frames = 3) { + await page.evaluate( + (frames) => + new Promise((resolve) => { + let remaining = frames + const tick = () => { + remaining-- + if (remaining <= 0) return resolve() + requestAnimationFrame(tick) + } + requestAnimationFrame(tick) + }), + frames, + ) + }, + async waitForPart(partID: string) { + await expect(page.locator(`[data-timeline-part-id="${partID}"]`).first()).toBeVisible() + }, + } +} + +function describeEvent(event: EventPayload) { + if (event.payload.type === "message.part.updated") { + const part = event.payload.properties.part + return [ + event.payload.type, + part.id, + part.type === "tool" ? part.tool : part.type, + part.type === "tool" ? part.state.status : undefined, + ] + .filter(Boolean) + .join(":") + } + if (event.payload.type === "session.status") { + const status = event.payload.properties.status + return [event.payload.type, status.type, status.type === "retry" ? status.attempt : undefined] + .filter((value) => value !== undefined) + .join(":") + } + return event.payload.type +} + +export function event( + type: Type, + properties: Extract["properties"], +): TimelineEvent +export function event(type: TimelinePayload["type"], properties: TimelinePayload["properties"]): TimelineEvent { + return validateTimelineEvent({ + directory, + payload: { id: `evt_timeline_${String(++eventSequence).padStart(4, "0")}`, type, properties }, + }) +} + +export function validateTimelineEvent(input: unknown): TimelineEvent { + return decodeEvent(input, decodeOptions) +} + +export function validateTimelineMessages(input: readonly TimelineMessage[]): TimelineMessage[] { + input.forEach((message) => decodeMessage(message, decodeOptions)) + const messages = [...input] + const messageIDs = new Set() + const partIDs = new Set() + const users = new Set(messages.filter((message) => message.info.role === "user").map((message) => message.info.id)) + + messages.forEach((message) => { + if (messageIDs.has(message.info.id)) + throw new Error(`Timeline fixture has duplicate message ID: ${message.info.id}`) + messageIDs.add(message.info.id) + if (message.info.role === "assistant" && !users.has(message.info.parentID)) + throw new Error(`Timeline assistant ${message.info.id} must reference a parent user in the fixture`) + message.parts.forEach((part) => { + if (part.sessionID !== message.info.sessionID || part.messageID !== message.info.id) + throw new Error(`Timeline part ${part.id} ownership does not match message ${message.info.id}`) + if (message.info.role === "user" && !["text", "file", "agent", "subtask"].includes(part.type)) + throw new Error(`Timeline user message ${message.info.id} cannot own ${part.type} part ${part.id}`) + if (message.info.role === "assistant" && ["agent", "subtask"].includes(part.type)) + throw new Error(`Timeline assistant message ${message.info.id} cannot own ${part.type} part ${part.id}`) + if (partIDs.has(part.id)) throw new Error(`Timeline fixture has duplicate part ID: ${part.id}`) + partIDs.add(part.id) + }) + }) + return messages +} + +export async function waitForVisualSettle(page: Page, selectors: string[], stableFrames = 3) { + await page.waitForFunction( + ({ selectors, stableFrames }) => { + const elements = selectors.map((selector) => document.querySelector(selector)) + if (elements.some((element) => !element)) return false + return new Promise((resolve) => { + let stable = 0 + let previous = "" + const sample = () => { + const signature = JSON.stringify( + elements.map((element) => { + const rect = element!.getBoundingClientRect() + return [Math.round(rect.top * 10), Math.round(rect.bottom * 10), Math.round(rect.height * 10)] + }), + ) + stable = signature === previous ? stable + 1 : 0 + previous = signature + const ordered = elements + .slice(1) + .every( + (element, index) => + elements[index]!.getBoundingClientRect().bottom <= element!.getBoundingClientRect().top + 0.5, + ) + if (stable >= stableFrames && ordered) return resolve(true) + requestAnimationFrame(sample) + } + requestAnimationFrame(sample) + }) + }, + { selectors, stableFrames }, + ) +} + +export function historyMessages(count: number): TimelineMessage[] { + return Array.from({ length: count }, (_, index) => { + const value = String(index).padStart(4, "0") + const historyUserID = `msg_0${value}_history_a_user` + return [ + userMessage(undefined, { id: historyUserID, created: 1690000000000 + index * 10_000 }), + assistantMessage( + [ + { + id: `prt_0${value}_history_text`, + type: "text", + text: `Historical response ${index}. ${"Existing session content keeps the virtual timeline realistic. ".repeat(5)}`, + }, + ], + { + id: `msg_0${value}_history_b_assistant`, + parentID: historyUserID, + created: 1690000001000 + index * 10_000, + }, + ), + ] + }).flat() +} + +export function partUpdated(part: Part | PartSeed<"assistant">) { + const owned = "messageID" in part ? part : { ...part, sessionID, messageID: assistantID } + decodePart(owned, decodeOptions) + return event("message.part.updated", { + sessionID, + part: owned, + time: 1700000002000, + }) +} + +export function partDelta(partID: string, delta: string, messageID = assistantID) { + return event("message.part.delta", { sessionID, messageID, partID, field: "text", delta }) +} + +export function messageUpdated(info: Message) { + return event("message.updated", { sessionID, info }) +} + +export function status(type: SessionStatus["type"], attempt = 1) { + return event("session.status", { + sessionID, + status: type === "retry" ? { type, attempt, message: "Rate limited", next: 1700000010000 } : { type }, + }) +} + +export function userMessage( + parts?: PartSeed<"user">[], + input: { id?: string; summary?: UserMessage["summary"]; created?: number } = {}, +): Extract { + const id = input.id ?? userID + const seeds = parts ?? [userText("Build the timeline stability matrix.", { id: `prt_${id}_text` })] + const message = { + info: { + id, + sessionID, + role: "user", + time: { created: input.created ?? 1700000000000 }, + summary: input.summary ?? { diffs: [] }, + agent: "build", + model, + }, + parts: seeds.map((part) => ({ + ...part, + sessionID, + messageID: id, + })), + } satisfies Extract + decodeMessage(message, decodeOptions) + return message +} + +export function assistantMessage( + parts: PartSeed<"assistant">[] = [], + input: { + id?: string + parentID?: string + completed?: boolean + error?: AssistantMessage["error"] + created?: number + } = {}, +): Extract { + const id = input.id ?? assistantID + const message = { + info: { + id, + sessionID, + role: "assistant", + time: { + created: input.created ?? 1700000001000, + ...(input.completed === false ? {} : { completed: (input.created ?? 1700000001000) + 1_000 }), + }, + parentID: input.parentID ?? userID, + modelID: model.modelID, + providerID: model.providerID, + mode: "build", + agent: "build", + path: { cwd: directory, root: directory }, + cost: 0.01, + tokens: { input: 100, output: 200, reasoning: 0, cache: { read: 0, write: 0 } }, + variant: "max", + ...(input.error ? { error: input.error } : {}), + }, + parts: parts.map((part) => ({ ...part, sessionID, messageID: id })), + } satisfies Extract + decodeMessage(message, decodeOptions) + return message +} + +export function userText( + text: string, + input: Partial, { type: "text" }>, "type" | "text">> = {}, +): Extract, { type: "text" }> { + return { id: "prt_user_text", type: "text", text, ...input } +} + +export function textPart(id: string, text: string): Extract, { type: "text" }> { + return { id, type: "text", text } +} + +export function reasoningPart(id: string, text: string): Extract, { type: "reasoning" }> { + return { id, type: "reasoning", text, time: { start: 1700000001000 } } +} + +export function toolPart( + id: string, + tool: string, + state: "pending", + input: Record, + options?: ToolOptions<"pending">, +): Omit +export function toolPart( + id: string, + tool: string, + state: "running", + input: Record, + options?: ToolOptions<"running">, +): Omit +export function toolPart( + id: string, + tool: string, + state: "completed", + input: Record, + options?: ToolOptions<"completed">, +): Omit +export function toolPart( + id: string, + tool: string, + state: "error", + input: Record, + options?: ToolOptions<"error">, +): Omit +export function toolPart( + id: string, + tool: string, + state: ToolStatus, + input: Record, + options: ToolOptions = {}, +): Omit { + const base = { id, type: "tool" as const, callID: `call_${id}`, tool } + if (state === "pending") return { ...base, state: { status: state, input, raw: "" } } + if (state === "running") + return { + ...base, + state: { + status: state, + input, + title: options.title, + metadata: options.metadata ?? {}, + time: { start: 1700000001000 }, + }, + } + if (state === "error") + return { + ...base, + state: { + status: state, + input, + error: options.error ?? "Tool failed", + metadata: options.metadata ?? {}, + time: { start: 1700000001000, end: 1700000002000 }, + }, + } + return { + ...base, + state: { + status: state, + input, + output: options.output ?? "Completed", + title: options.title ?? tool, + metadata: options.metadata ?? {}, + time: { start: 1700000001000, end: 1700000002000 }, + }, + } +} + +export function shell( + id: string, + state: ToolStatus, + output = "", + command = `echo ${id}`, +): Omit { + if (state === "pending") return toolPart(id, "bash", state, { command }) + if (state === "running") + return toolPart(id, "bash", state, { command }, { title: command, metadata: { command, output } }) + if (state === "error") + return toolPart(id, "bash", state, { command }, { error: output || undefined, metadata: { command, output } }) + return toolPart(id, "bash", state, { command }, { title: command, output, metadata: { command, output } }) +} + +export function completedAssistantInfo(info: AssistantMessage): AssistantMessage { + return { ...info, time: { ...info.time, completed: 1700000003000 } } +} + +export function project() { + return { + id: projectID, + worktree: directory, + vcs: "git", + name: "timeline-stability", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + } +} + +export function session(input: Partial = {}): Session { + return { + id: sessionID, + slug: "timeline-stability", + projectID, + directory, + title, + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + ...input, + } +} + +function eventSchema< + const Type extends TimelinePayload["type"], + const Properties extends Schema.Codec, +>(type: Type, properties: Properties) { + return Schema.Struct({ + directory: Schema.String, + project: Schema.optional(Schema.String), + workspace: Schema.optional(Schema.String), + payload: Schema.Struct({ id: Event.ID, type: Schema.Literal(type), properties }), + }) +} + +function provider() { + return { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "claude-opus-4-6" }, + } +} diff --git a/packages/app/e2e/performance/timeline-stability/interaction.spec.ts b/packages/app/e2e/performance/timeline-stability/interaction.spec.ts new file mode 100644 index 000000000000..8cdf4fa8cfa8 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/interaction.spec.ts @@ -0,0 +1,242 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { assistantMessage, setupTimeline, shell, textPart, toolPart, userMessage, waitForVisualSettle } from "./fixture" + +test("expands and collapses a long completed shell without overlap", async ({ page }, testInfo) => { + const shellID = "prt_interaction_01_shell" + const followingID = "prt_interaction_02_following" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "completed", lines(50)), textPart(followingID, "Following shell expansion")]), + ], + settings: { shellToolPartsExpanded: false }, + cpuRate: 4, + seedHistory: true, + }) + const trigger = page.locator(`[data-timeline-part-id="${shellID}"] [data-slot="collapsible-trigger"]`) + await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + const plan = visualPlan(regions, [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ]) + await startVisualProbe(page, regions) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await page.waitForTimeout(500) + const expanded = await stopVisualProbe(page) + await reportVisualStability(testInfo, "shell-expand", expanded, plan) + + await startVisualProbe(page, regions) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "false") + await page.waitForTimeout(500) + const collapsed = await stopVisualProbe(page) + await reportVisualStability(testInfo, "shell-collapse", collapsed, plan) +}) + +test("expands and collapses a completed context group without overlap", async ({ page }, testInfo) => { + const ids = [ + "prt_interaction_01_read", + "prt_interaction_02_glob", + "prt_interaction_03_grep", + "prt_interaction_04_list", + ] + const group = `[data-timeline-part-ids="${ids.join(",")}"]` + const followingID = "prt_interaction_context_following" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(ids[0]!, "read", "completed", { filePath: "src/a.ts" }), + toolPart(ids[1]!, "glob", "completed", { path: ".", pattern: "**/*.ts" }), + toolPart(ids[2]!, "grep", "completed", { path: ".", pattern: "stable" }), + toolPart(ids[3]!, "list", "completed", { path: "src" }), + textPart(followingID, "Following context expansion"), + ]), + ], + cpuRate: 4, + seedHistory: true, + }) + const trigger = page.locator(`${group} [data-slot="collapsible-trigger"]`) + await waitForVisualSettle(page, [group, `[data-timeline-part-id="${followingID}"]`]) + for (const [name, expanded] of [ + ["context-expand", true], + ["context-collapse", false], + ["context-reexpand", true], + ] as const) { + const regions = defineVisualRegions({ + context: { selector: group, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", String(expanded)) + await page.waitForTimeout(500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + name, + trace, + visualPlan(regions, [ + { type: "required", regions: ["context", "following"] }, + { type: "unique", regions: ["context", "following"] }, + { type: "stable", regions: ["context", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["context", "following"] }, + ]), + ) + } +}) + +test("expands and collapses an edit diff without moving twice", async ({ page }, testInfo) => { + const editID = "prt_interaction_edit" + const followingID = "prt_interaction_edit_following" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart( + editID, + "edit", + "completed", + { filePath: "src/edit.ts" }, + { + metadata: { + filediff: { + file: "src/edit.ts", + additions: 40, + deletions: 40, + before: source(40, false), + after: source(40, true), + }, + }, + }, + ), + textPart(followingID, "Following edit expansion"), + ]), + ], + settings: { editToolPartsExpanded: false }, + cpuRate: 4, + seedHistory: true, + }) + const trigger = page.locator(`[data-timeline-part-id="${editID}"] [data-slot="collapsible-trigger"]`).first() + await waitForVisualSettle(page, [`[data-timeline-part-id="${editID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + edit: { selector: `[data-timeline-part-id="${editID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await page.waitForTimeout(900) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "edit-expand", + trace, + visualPlan(regions, [ + { type: "required", regions: ["edit", "following"] }, + { type: "unique", regions: ["edit", "following"] }, + { type: "stable", regions: ["edit", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0, maxReversals: 1 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["edit", "following"] }, + ]), + ) +}) + +test("shows all and expands historical diff summary without overlap", async ({ page }, testInfo) => { + const firstUser = userMessage(undefined, { + summary: { + diffs: Array.from({ length: 12 }, (_, index) => ({ + file: `src/diff-${index}.ts`, + additions: 1, + deletions: 1, + patch: `@@ -1 +1 @@\n-export const value = ${index}\n+export const value = ${index + 1}`, + })), + }, + }) + const nextUserID = "msg_2000_diff_interaction_user" + await setupTimeline(page, { + messages: [ + firstUser, + assistantMessage(), + userMessage(undefined, { id: nextUserID, created: 1700000010000 }), + assistantMessage([], { + id: "msg_2001_diff_interaction_assistant", + parentID: nextUserID, + created: 1700000011000, + }), + ], + cpuRate: 4, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate((element) => (element.scrollTop = 0)) + const diff = page.locator('[data-timeline-row="DiffSummary"]') + const following = page.locator(`[data-message-id="${nextUserID}"]`).first() + await expect(diff).toBeVisible() + const regions = defineVisualRegions({ + diff: { selector: '[data-timeline-row="DiffSummary"]' }, + following: { selector: `[data-message-id="${nextUserID}"]` }, + }) + await startVisualProbe(page, regions) + await page.getByText(/show all/i).click() + await page.waitForTimeout(500) + await diff.locator('[data-slot="session-turn-diff-trigger"]').first().click() + await page.waitForTimeout(900) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "diff-summary-expand", + trace, + visualPlan(regions, [ + { type: "required", regions: ["diff", "following"] }, + { type: "unique", regions: ["diff", "following"] }, + { type: "stable", regions: ["diff", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 1, maxReversals: 2 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["diff", "following"] }, + ]), + ) +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} + +function source(count: number, changed: boolean) { + return Array.from( + { length: count }, + (_, index) => `export const value${index} = ${changed ? index + 1 : index}\n`, + ).join("") +} diff --git a/packages/app/e2e/performance/timeline-stability/lifecycle.spec.ts b/packages/app/e2e/performance/timeline-stability/lifecycle.spec.ts new file mode 100644 index 000000000000..40688429a33e --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/lifecycle.spec.ts @@ -0,0 +1,157 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + mapVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + completedAssistantInfo, + messageUpdated, + partDelta, + partUpdated, + reasoningPart, + setupTimeline, + shell, + status, + textPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +test.describe("timeline visual lifecycle stability", () => { + test("streams empty, short, and long parallel shells to staggered completion", async ({ page }, testInfo) => { + test.setTimeout(180_000) + const ids = ["prt_parallel_01_empty", "prt_parallel_02_short", "prt_parallel_03_long"] as const + const initial = ids.map((id) => shell(id, "running")) + const followingID = "prt_parallel_04_following" + const assistant = assistantMessage([...initial, textPart(followingID, "Following all parallel shells.")], { + completed: false, + }) + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistant], + settings: { shellToolPartsExpanded: true, showReasoningSummaries: true }, + cpuRate: 4, + eventRetry: 24, + seedHistory: true, + }) + await timeline.send(status("busy"), 150) + for (const id of ids) await timeline.waitForPart(id) + const scroller = page.locator(".scroll-view__viewport", { + has: page.locator('[data-timeline-row="AssistantPart"]'), + }) + await scroller.evaluate((element) => (element.scrollTop = element.scrollHeight)) + const regions = defineVisualRegions({ + prt_shell_empty: shellRegion(ids[0]), + prt_shell_short: shellRegion(ids[1]), + prt_shell_long: shellRegion(ids[2]), + following: shellRegion(followingID), + }) + await waitForVisualSettle(page, [`[data-timeline-part-id="${followingID}"]`]) + await startVisualProbe(page, regions) + await timeline.sendAll([ + { event: partUpdated(shell(ids[0]!, "completed", "")), delay: 180 }, + { event: partUpdated(shell(ids[2]!, "running", lines(10))), delay: 70 }, + { event: partUpdated(shell(ids[1]!, "running", lines(2))), delay: 110 }, + { event: partUpdated(shell(ids[2]!, "running", lines(25))), delay: 80 }, + { event: partUpdated(shell(ids[1]!, "completed", lines(2))), delay: 260 }, + { event: partUpdated(shell(ids[2]!, "running", lines(50))), delay: 100 }, + { event: partUpdated(shell(ids[2]!, "completed", lines(50))), delay: 450 }, + { event: messageUpdated(completedAssistantInfo(assistant.info)), delay: 100 }, + { event: status("idle"), delay: 700 }, + ]) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "parallel-shells", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["prt_shell_empty", "prt_shell_short", "prt_shell_long", "following"] }, + { type: "unique", regions: ["prt_shell_empty", "prt_shell_short", "prt_shell_long"] }, + { type: "stable", regions: ["prt_shell_empty", "prt_shell_short", "prt_shell_long", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0, maxReversals: 4 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["prt_shell_empty", "prt_shell_short", "prt_shell_long", "following"] }, + ], + { perMarker: true }, + ), + ) + await expect(page.locator(`[data-timeline-part-id="${ids[2]}"] [data-slot="bash-pre"]`)).toContainText("line 50") + + const short = page.locator(`[data-timeline-part-id="${ids[1]}"]`) + await short.locator('[data-slot="collapsible-trigger"]').click() + await expect(short.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "false") + await timeline.send(partUpdated(textPart("prt_late_sibling", "A later sibling rerender.")), 250) + await expect(short.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "false") + }) + + test("replaces thinking with streamed reasoning and text without a blank visible turn", async ({ + page, + }, testInfo) => { + const reasoningID = "prt_reasoning_visible" + const textID = "prt_streamed_text" + const assistant = assistantMessage([], { completed: false }) + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistant], + settings: { showReasoningSummaries: true }, + cpuRate: 4, + }) + await timeline.send(status("busy"), 120) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + + const regions = defineVisualRegions({ + thinking: { selector: '[data-timeline-row="Thinking"]' }, + reasoning: { + selector: `[data-timeline-part-id="${reasoningID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + text: { selector: `[data-timeline-part-id="${textID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(reasoningPart(reasoningID, "")), 100) + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0) + await timeline.send(partUpdated(reasoningPart(reasoningID, "## Planning\n\nChecking the visible timeline.")), 160) + await timeline.waitForPart(reasoningID) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await timeline.send(partUpdated(textPart(textID, "Starting")), 100) + await timeline.send(partDelta(textID, " **stable"), 90) + await timeline.send(partDelta(textID, " output** with `code` and [a link"), 130) + await timeline.send(partDelta(textID, "](https://example.com)."), 220) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 120) + await timeline.send(status("idle"), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "reasoning-text-handoff", + trace, + visualPlan(regions, [ + { type: "required", regions: ["reasoning", "text"] }, + { type: "continuous-any", regions: ["thinking", "reasoning", "text"] }, + { type: "unique", regions: ["reasoning", "text"] }, + { type: "stable", regions: ["reasoning", "text"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxReversals: 4 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["reasoning", "text"] }, + ]), + ) + await expect(page.locator(`[data-timeline-part-id="${textID}"]`)).toContainText("stable output") + }) +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} + +function shellRegion(id: string) { + return { selector: `[data-timeline-part-id="${id}"]`, closest: '[data-timeline-row="AssistantPart"]' } +} diff --git a/packages/app/e2e/performance/timeline-stability/oracle-browser.spec.ts b/packages/app/e2e/performance/timeline-stability/oracle-browser.spec.ts new file mode 100644 index 000000000000..7891eba5bd97 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/oracle-browser.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from "@playwright/test" +import { + analyzeVisualObservations, + defineVisualRegions, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { assistantMessage, setupTimeline, textPart, userMessage } from "./fixture" + +test("detects blanking caused by ancestor opacity", async ({ page }) => { + const partID = "prt_oracle_ancestor_opacity" + await setupTimeline(page, { messages: [userMessage(), assistantMessage([textPart(partID, "Visible content")])] }) + const row = page.locator(`[data-timeline-part-id="${partID}"]`).first() + const regions = defineVisualRegions({ + content: { selector: `[data-timeline-part-id="${partID}"]` }, + }) + await startVisualProbe(page, regions) + await row.evaluate((element) => { + element.parentElement!.style.opacity = "0" + }) + await page.waitForTimeout(50) + await row.evaluate((element) => { + element.parentElement!.style.opacity = "1" + }) + await page.waitForTimeout(50) + const trace = await stopVisualProbe(page) + const issues = analyzeVisualObservations( + trace.samples, + visualPlan(regions, [ + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all" }, + { type: "label-stability", regions: "all" }, + ]), + ) + + expect(issues.some((issue) => issue.includes("blanked between visible frames"))).toBe(true) +}) + +test("detects root opacity when probing descendant opacity", async ({ page }) => { + const partID = "prt_oracle_descendant_opacity" + await setupTimeline(page, { messages: [userMessage(), assistantMessage([textPart(partID, "Visible content")])] }) + const row = page.locator(`[data-timeline-part-id="${partID}"]`).first() + await row.evaluate((element) => { + element.innerHTML = 'Visible content' + }) + const regions = defineVisualRegions({ + content: { + selector: `[data-timeline-part-id="${partID}"]`, + opacitySelectors: ['[data-probe-opacity="true"]'], + }, + }) + await startVisualProbe(page, regions) + await row.evaluate((element) => { + ;(element as HTMLElement).style.opacity = "0" + }) + await page.waitForTimeout(50) + await row.evaluate((element) => { + ;(element as HTMLElement).style.opacity = "1" + }) + await page.waitForTimeout(50) + const trace = await stopVisualProbe(page) + const issues = analyzeVisualObservations( + trace.samples, + visualPlan(regions, [ + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all" }, + { type: "label-stability", regions: "all" }, + ]), + ) + + expect(issues.some((issue) => issue.includes("blanked between visible frames"))).toBe(true) +}) diff --git a/packages/app/e2e/performance/timeline-stability/playwright.config.ts b/packages/app/e2e/performance/timeline-stability/playwright.config.ts new file mode 100644 index 000000000000..cfa4d926132d --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/playwright.config.ts @@ -0,0 +1,17 @@ +import config from "../playwright.config" + +export default { + ...config, + testDir: ".", + testMatch: "**/*.spec.ts", + outputDir: "../../test-results/timeline-stability", + reporter: [["html", { outputFolder: "../../playwright-report/timeline-stability", open: "never" }], ["line"]], + retries: 0, + workers: 1, + use: { + ...config.use, + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, +} diff --git a/packages/app/e2e/performance/timeline-stability/scroll-interaction.spec.ts b/packages/app/e2e/performance/timeline-stability/scroll-interaction.spec.ts new file mode 100644 index 000000000000..e67545104d48 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/scroll-interaction.spec.ts @@ -0,0 +1,353 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + shell, + textPart, + userMessage, + type TimelineMessage, +} from "./fixture" + +test("does not reverse visible rows when the user wheels during shell remeasurement", async ({ page }, testInfo) => { + const shellID = "prt_wheel_01_shell" + const followingID = "prt_wheel_02_following" + const timeline = await setupTimeline(page, { + messages: [ + ...history(12), + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following wheel interaction")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + reducedMotion: true, + seedHistory: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "running", lines(30))), 80) + await scroller.evaluate((element) => + element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: -180 })), + ) + await scroller.evaluate((element) => (element.scrollTop -= 180)) + await timeline.send(partUpdated(shell(shellID, "running", lines(50))), 250) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, "wheel-during-resize", trace, rowPairPlan(regions, 1)) +}) + +test("keeps moving upward while drag-selecting above the timeline", async ({ page }) => { + await setupTimeline(page, { + messages: history(80), + viewport: { width: 1400, height: 700 }, + reducedMotion: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + const text = page.getByText("History 79.", { exact: false }) + await expect(text).toBeVisible() + await scroller.evaluate((element) => { + element.dataset.selectionLength = "0" + document.addEventListener("selectionchange", () => { + element.dataset.selectionLength = String( + Math.max(Number(element.dataset.selectionLength), window.getSelection()?.toString().length ?? 0), + ) + }) + }) + const textBox = await text.boundingBox() + const scrollBox = await scroller.boundingBox() + expect(textBox).not.toBeNull() + expect(scrollBox).not.toBeNull() + if (!textBox || !scrollBox) return + + await page.mouse.move(textBox.x + textBox.width - 10, textBox.y + textBox.height / 2) + await page.mouse.down() + await page.mouse.move(textBox.x + 20, scrollBox.y - 120, { steps: 30 }) + + await expect.poll(() => scroller.evaluate((element) => Number(element.dataset.selectionLength))).toBeGreaterThan(0) + await expect + .poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) + .toBeGreaterThan(500) + await page.mouse.up() +}) + +test("does not pull a keyboard-scrolled user during shell remeasurement", async ({ page }, testInfo) => { + const shellID = "prt_keyboard_01_shell" + const followingID = "prt_keyboard_02_following" + const timeline = await setupTimeline(page, { + messages: [ + ...history(12), + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following keyboard interaction")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.focus() + for (let index = 0; index < 3; index++) { + await scroller.press("PageUp") + await page.waitForTimeout(250) + } + await expect + .poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop), { + timeout: 20_000, + }) + .toBeGreaterThan(80) + await page.waitForFunction(() => { + const root = [...document.querySelectorAll(".scroll-view__viewport")].find((element) => + element.querySelector("[data-timeline-row]"), + ) + if (!root) return false + return new Promise((resolve) => { + const top = root.scrollTop + requestAnimationFrame(() => requestAnimationFrame(() => resolve(Math.abs(root.scrollTop - top) <= 0.5))) + }) + }) + const anchor = await scroller.evaluate((element) => { + const view = element.getBoundingClientRect() + return [...element.querySelectorAll("[data-timeline-key]")].find((row) => { + const rect = row.getBoundingClientRect() + return rect.top >= view.top + 40 && rect.bottom <= view.bottom - 40 + })?.dataset.timelineKey + }) + expect(anchor).toBeTruthy() + const regions = defineVisualRegions({ + anchor: { selector: `[data-timeline-key="${anchor}"]` }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "running", lines(50))), 400) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, "keyboard-during-resize", trace, anchorPlan(regions)) +}) + +test("tracks keyboard scrolling from a focused timeline descendant", async ({ page }, testInfo) => { + const shellID = "prt_descendant_keyboard_01_shell" + const timeline = await setupTimeline(page, { + messages: [...history(12), userMessage(), assistantMessage([shell(shellID, "completed", lines(5))])], + settings: { shellToolPartsExpanded: false }, + cpuRate: 4, + reducedMotion: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + const row = page.locator(`[data-timeline-part-id="${shellID}"]`).first() + const trigger = page.locator(`[data-timeline-part-id="${shellID}"] [data-slot="collapsible-trigger"]`) + await row.evaluate((element) => element.setAttribute("tabindex", "0")) + await row.focus() + for (let index = 0; index < 3; index++) { + await row.press("PageUp") + await page.waitForTimeout(250) + } + await expect + .poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) + .toBeGreaterThan(5) + const anchor = await scroller.evaluate((element) => { + const view = element.getBoundingClientRect() + return [...element.querySelectorAll("[data-timeline-key]")].find((row) => { + const rect = row.getBoundingClientRect() + return rect.top >= view.top + 40 && rect.bottom <= view.bottom - 40 + })?.dataset.timelineKey + }) + expect(anchor).toBeTruthy() + const regions = defineVisualRegions({ + anchor: { selector: `[data-timeline-key="${anchor}"]` }, + }) + await startVisualProbe(page, regions) + await trigger.click() + await page.waitForTimeout(300) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, "descendant-keyboard-resize", trace, anchorPlan(regions)) +}) + +test("does not claim keyboard scrolling owned by a nested scrollable", async ({ page }) => { + const shellID = "prt_nested_keyboard_shell" + await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(shellID, "completed", lines(50))])], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + reducedMotion: true, + seedHistory: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + const nested = page.locator(`[data-timeline-part-id="${shellID}"] [data-scrollable]`) + await nested.evaluate((element) => (element.scrollTop = element.scrollHeight)) + await nested.focus() + await page.waitForFunction(() => { + const root = [...document.querySelectorAll(".scroll-view__viewport")].find((element) => + element.querySelector("[data-timeline-row]"), + ) + if (!root) return false + return new Promise((resolve) => { + const top = root.scrollTop + requestAnimationFrame(() => requestAnimationFrame(() => resolve(Math.abs(root.scrollTop - top) <= 0.5))) + }) + }) + const before = await scroller.evaluate((element) => element.scrollTop) + const nestedBefore = await nested.evaluate((element) => element.scrollTop) + await nested.press("PageUp") + await page.waitForTimeout(300) + expect(await scroller.evaluate((element) => element.scrollTop)).toBe(before) + expect(await nested.evaluate((element) => element.scrollTop)).toBeLessThan(nestedBefore) + + await nested.evaluate((element) => (element.scrollTop = 0)) + await scroller.evaluate((element) => (element.scrollTop = Math.min(300, element.scrollHeight - element.clientHeight))) + const boundaryBefore = await scroller.evaluate((element) => element.scrollTop) + expect(boundaryBefore).toBeGreaterThan(0) + await nested.press("PageUp") + await expect.poll(() => scroller.evaluate((element) => element.scrollTop)).toBeLessThan(boundaryBefore) + + const nonOverflowing = page.locator(`[data-timeline-part-id="${shellID}"]`).first() + await nonOverflowing.evaluate((element) => { + element.setAttribute("data-scrollable", "") + element.setAttribute("tabindex", "0") + }) + await nonOverflowing.focus() + const nonOverflowBefore = await scroller.evaluate((element) => element.scrollTop) + await nonOverflowing.press("PageUp") + await expect.poll(() => scroller.evaluate((element) => element.scrollTop)).toBeLessThan(nonOverflowBefore) +}) + +test("jump to latest lands on stable final rows after offscreen growth", async ({ page }, testInfo) => { + const shellID = "prt_jump_01_shell" + const followingID = "prt_jump_02_following" + const timeline = await setupTimeline(page, { + messages: [ + ...history(20), + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Latest visible row")], { completed: false }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate( + (element) => (element.scrollTop = Math.max(0, element.scrollHeight - element.clientHeight - 600)), + ) + await timeline.send(partUpdated(shell(shellID, "running", lines(50))), 300) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await page.getByRole("button", { name: /Jump to latest/i }).click() + await expect(page.locator(`[data-timeline-part-id="${followingID}"]`)).toBeVisible() + await page.waitForTimeout(600) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "jump-latest", + trace, + visualPlan(regions, [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 1 }, + { type: "label-stability", regions: "all" }, + { type: "acquire-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ]), + ) +}) + +test("handles a single row taller than the viewport", async ({ page }, testInfo) => { + const shellID = "prt_tall_01_shell" + const followingID = "prt_tall_02_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "After tall row")], { completed: false }), + ], + settings: { shellToolPartsExpanded: true }, + viewport: { width: 900, height: 360 }, + cpuRate: 4, + seedHistory: true, + }) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "completed", lines(100))), 700) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "taller-than-viewport", + trace, + visualPlan(regions, [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ]), + ) +}) + +function history(count: number): TimelineMessage[] { + return Array.from({ length: count }, (_, index) => { + const prefix = `msg_${String(index).padStart(4, "0")}_scroll` + const userID = `${prefix}_a_user` + return [ + userMessage(undefined, { id: userID, created: 1690000000000 + index * 10_000 }), + assistantMessage( + [textPart(`prt_${String(index).padStart(4, "0")}_scroll`, `History ${index}. ${"content ".repeat(30)}`)], + { + id: `${prefix}_b_assistant`, + parentID: userID, + created: 1690000001000 + index * 10_000, + }, + ), + ] + }).flat() +} + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} + +function rowPairPlan( + regions: Record<"shell" | "following", { selector: string; closest?: string }>, + maxPositionReversals: number, +) { + return visualPlan(regions, [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["shell", "following"] }, + ]) +} + +function anchorPlan(regions: Record<"anchor", { selector: string; closest?: string }>) { + return visualPlan(regions, [ + { type: "required", regions: ["anchor"] }, + { type: "unique", regions: ["anchor"] }, + { type: "stable", regions: ["anchor"] }, + { type: "fixed", regions: ["anchor"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + ]) +} diff --git a/packages/app/e2e/performance/timeline-stability/shell-matrix.spec.ts b/packages/app/e2e/performance/timeline-stability/shell-matrix.spec.ts new file mode 100644 index 000000000000..bab1ca23b4f9 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/shell-matrix.spec.ts @@ -0,0 +1,255 @@ +import { test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + setupTimeline, + shell, + textPart, + userMessage, + waitForVisualSettle, +} from "./fixture" + +const profiles = [ + { + name: "empty running to completed", + updates: [{ state: "completed" as const, output: "", delay: 350 }], + }, + { + name: "50 lines arriving incrementally", + updates: [ + { state: "running" as const, output: lines(1), delay: 100 }, + { state: "running" as const, output: lines(10), delay: 160 }, + { state: "running" as const, output: lines(25), delay: 90 }, + { state: "running" as const, output: lines(50), delay: 220 }, + { state: "completed" as const, output: lines(50), delay: 500 }, + ], + }, + { + name: "wide ANSI and CRLF output", + updates: [ + { + state: "running" as const, + output: Array.from({ length: 20 }, (_, index) => `\u001b[32mline ${index}\u001b[0m ${"wide-".repeat(30)}`).join( + "\r\n", + ), + delay: 240, + }, + { + state: "completed" as const, + output: Array.from({ length: 20 }, (_, index) => `line ${index} ${"wide-".repeat(30)}`).join("\n"), + delay: 500, + }, + ], + }, +] as const + +for (const profile of profiles) { + test(`keeps rows stable for shell ${profile.name}`, async ({ page }, testInfo) => { + const shellID = `prt_matrix_${profiles.indexOf(profile)}_01_shell` + const followingID = `prt_matrix_${profiles.indexOf(profile)}_02_following` + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following shell row")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + seedHistory: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate((element) => (element.scrollTop = element.scrollHeight)) + await waitForVisualSettle(page, [ + `[data-timeline-part-id="${shellID}"]`, + `[data-timeline-part-id="${followingID}"]`, + ]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + for (const update of profile.updates) { + await timeline.send(partUpdated(shell(shellID, update.state, update.output)), update.delay) + } + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + `shell-${profiles.indexOf(profile)}`, + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0, maxReversals: 1 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ), + ) + }) +} + +test("keeps following row stable when a collapsed shell receives 50 lines", async ({ page }, testInfo) => { + const shellID = "prt_matrix_collapsed_01_shell" + const followingID = "prt_matrix_collapsed_02_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running"), textPart(followingID, "Following collapsed shell")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: false }, + cpuRate: 4, + seedHistory: true, + }) + await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(shell(shellID, "running", lines(50))), 240) + await timeline.send(partUpdated(shell(shellID, "completed", lines(50))), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "collapsed-shell", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: ["following"], maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ), + ) +}) + +test("keeps rows stable when a running shell becomes an error", async ({ page }, testInfo) => { + const shellID = "prt_matrix_error_01_shell" + const followingID = "prt_matrix_error_02_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([shell(shellID, "running", lines(10)), textPart(followingID, "Following failed shell")], { + completed: false, + }), + ], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + seedHistory: true, + }) + await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`, `[data-timeline-part-id="${followingID}"]`]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + partUpdated({ + ...shell(shellID, "error"), + state: { + status: "error", + input: { command: `echo ${shellID}` }, + error: "Command failed after output", + metadata: {}, + time: { start: 1700000001000, end: 1700000002000 }, + }, + }), + 500, + ) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "shell-error", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: ["following"], maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ), + ) +}) + +test("keeps rows stable when later text arrives before shell output", async ({ page }, testInfo) => { + const shellID = "prt_late_text_01_shell" + const followingID = "prt_late_text_02_following" + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(shellID, "running")], { completed: false })], + settings: { shellToolPartsExpanded: true }, + cpuRate: 4, + seedHistory: true, + }) + await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`]) + const regions = defineVisualRegions({ + shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(textPart(followingID, "Later assistant content arrived before shell output.")), 240) + await timeline.send(partUpdated(shell(shellID, "running", lines(20))), 300) + await timeline.send(partUpdated(shell(shellID, "completed", lines(20))), 600) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "late-text-before-shell-output", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["shell", "following"] }, + { type: "unique", regions: ["shell", "following"] }, + { type: "stable", regions: ["shell"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["shell", "following"] }, + ], + { perMarker: true }, + ), + ) +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} diff --git a/packages/app/e2e/performance/timeline-stability/tool-mutation.spec.ts b/packages/app/e2e/performance/timeline-stability/tool-mutation.spec.ts new file mode 100644 index 000000000000..03690ec92147 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/tool-mutation.spec.ts @@ -0,0 +1,106 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + partUpdated, + session, + sessionID, + setupTimeline, + textPart, + toolPart, + userMessage, +} from "./fixture" + +test("adds a task child-session link without replacing the task row", async ({ page }, testInfo) => { + const taskID = "prt_task_link" + const childID = "ses_task_child" + const input = { description: "Inspect child", subagent_type: "explore" } + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([toolPart(taskID, "task", "running", input)], { completed: false })], + sessions: [session(), session({ id: childID, parentID: sessionID, title: "Inspect child" })], + cpuRate: 4, + }) + const regions = defineVisualRegions({ + task: { selector: `[data-timeline-part-id="${taskID}"] [data-slot="collapsible-trigger"]` }, + }) + await startVisualProbe(page, regions) + await timeline.send( + partUpdated(toolPart(taskID, "task", "completed", input, { metadata: { sessionId: childID } })), + 500, + ) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "task-link", + trace, + visualPlan(regions, [ + { type: "required", regions: ["task"] }, + { type: "unique", regions: ["task"] }, + { type: "stable", regions: ["task"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + ]), + ) + await expect( + page.locator(`a[href$="/session/${childID}"]`, { has: page.locator('[data-component="task-tool-card"]') }), + ).toBeVisible() +}) + +test("changes generic tool arguments without replacing the row", async ({ page }, testInfo) => { + const toolID = "prt_generic_mutation" + const followingID = "prt_generic_mutation_following" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [ + toolPart(toolID, "mcp_probe", "running", { target: "one", count: 1 }), + textPart(followingID, "Following generic tool"), + ], + { completed: false }, + ), + ], + cpuRate: 4, + }) + const regions = defineVisualRegions({ + tool: { selector: `[data-timeline-part-id="${toolID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + partUpdated(toolPart(toolID, "mcp_probe", "running", { target: "two", count: 2, mode: "deep" })), + 200, + ) + await timeline.send( + partUpdated(toolPart(toolID, "mcp_probe", "completed", { target: "two", count: 2, mode: "deep" })), + 400, + ) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "generic-mutation", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["tool", "following"] }, + { type: "unique", regions: ["tool", "following"] }, + { type: "stable", regions: ["tool", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["tool", "following"] }, + ], + { perMarker: true }, + ), + ) +}) diff --git a/packages/app/e2e/performance/timeline-stability/tools.spec.ts b/packages/app/e2e/performance/timeline-stability/tools.spec.ts new file mode 100644 index 000000000000..d26968e1cbc5 --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/tools.spec.ts @@ -0,0 +1,194 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantMessage, + directory, + partUpdated, + session, + sessionID, + setupTimeline, + status, + textPart, + toolPart, + userMessage, +} from "./fixture" + +test.describe("timeline tool state stability", () => { + test("moves lightweight tools through pending, running, and completed without replacing rows", async ({ + page, + }, testInfo) => { + const ids = ["webfetch", "websearch", "task", "skill", "custom"] as const + const inputs = { + webfetch: { url: "https://example.com/docs" }, + websearch: { query: "timeline stability" }, + task: { description: "Inspect timeline", subagent_type: "explore" }, + skill: { name: "stability" }, + custom: { target: "timeline", depth: 2 }, + } + const names = { webfetch: "webfetch", websearch: "websearch", task: "task", skill: "skill", custom: "mcp_probe" } + const questionID = "prt_state_question" + const initial = [ + ...ids.map((id) => toolPart(`prt_state_${id}`, names[id], "pending", inputs[id])), + toolPart(questionID, "question", "pending", questionInput()), + textPart("prt_state_following", "Following lightweight tools"), + ] + const childID = "ses_timeline_child" + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage(initial, { completed: false })], + sessions: [session(), session({ id: childID, parentID: sessionID, title: "Inspect timeline" })], + cpuRate: 4, + }) + await timeline.send(status("busy"), 120) + for (const id of ids) await timeline.waitForPart(`prt_state_${id}`) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0) + + const regionIDs = [ + "prt_state_webfetch", + "prt_state_websearch", + "prt_state_task", + "prt_state_skill", + "prt_state_custom", + ] as const + const regions = defineVisualRegions({ + prt_state_webfetch: toolRegion(regionIDs[0]), + prt_state_websearch: toolRegion(regionIDs[1]), + prt_state_task: toolRegion(regionIDs[2]), + prt_state_skill: toolRegion(regionIDs[3]), + prt_state_custom: toolRegion(regionIDs[4]), + }) + await startVisualProbe(page, regions) + for (const [index, id] of ids.entries()) { + await timeline.send( + partUpdated(toolPart(`prt_state_${id}`, names[id], "running", inputs[id])), + [80, 240, 100, 360, 140][index], + ) + } + for (const [index, id] of ["skill", "webfetch", "custom", "task", "websearch"].entries()) { + const key = id as (typeof ids)[number] + const metadata = key === "task" ? { sessionId: childID } : key === "websearch" ? { provider: "exa" } : {} + const output = key === "websearch" ? "Result https://example.com/result" : "Completed" + await timeline.send( + partUpdated(toolPart(`prt_state_${key}`, names[key], "completed", inputs[key], { metadata, output })), + [110, 70, 280, 130, 420][index], + ) + } + await timeline.send( + partUpdated( + toolPart(questionID, "question", "completed", questionInput(), { metadata: { answers: [["Keep it stable"]] } }), + ), + 350, + ) + await timeline.waitForPart(questionID) + await timeline.send(status("idle"), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "lightweight-tools", + trace, + visualPlan(regions, [ + { type: "required", regions: regionIDs }, + { type: "unique", regions: regionIDs }, + { type: "stable", regions: regionIDs }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxReversals: 4 }, + { type: "label-stability", regions: "all" }, + ]), + ) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toContainText("Keep it stable") + await expect( + page.locator(`a[href$="/session/${childID}"]`, { has: page.locator('[data-component="task-tool-card"]') }), + ).toBeVisible() + await expect(page.getByRole("button", { name: /Exa Web Search/ })).toBeVisible() + }) + + test("keeps an expanded mixed context group stable through staggered completion and error", async ({ + page, + }, testInfo) => { + const ids = ["prt_ctx_01_read", "prt_ctx_02_glob", "prt_ctx_03_grep", "prt_ctx_04_list"] + const tools = ["read", "glob", "grep", "list"] + const inputs = [ + { filePath: "src/a.ts", offset: 0, limit: 120 }, + { path: directory, pattern: "**/*.ts" }, + { path: directory, pattern: "stability", include: "*.ts" }, + { path: "src" }, + ] + const context = ids.map((id, index) => toolPart(id, tools[index]!, "pending", inputs[index]!)) + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([...context, textPart("prt_ctx_following", "Following context")], { completed: false }), + ], + cpuRate: 4, + }) + await timeline.send(status("busy"), 100) + const groupSelector = `[data-timeline-part-ids="${ids.join(",")}"]` + const group = page.locator(groupSelector) + await expect(group).toBeVisible() + await group.locator('[data-slot="collapsible-trigger"]').click() + await expect(group.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true") + + const regions = defineVisualRegions({ + status: { + selector: `${groupSelector} [data-component="tool-status-title"]`, + opacitySelectors: ['[data-slot="tool-status-active"]', '[data-slot="tool-status-done"]'], + }, + context: { selector: groupSelector, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: '[data-timeline-part-id="prt_ctx_following"]', + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + for (const [index, delay] of [90, 260, 70, 380].entries()) { + await timeline.send(partUpdated(toolPart(ids[index]!, tools[index]!, "running", inputs[index]!)), delay) + } + await timeline.send(partUpdated(toolPart(ids[1]!, tools[1]!, "completed", inputs[1]!)), 130) + await timeline.send(partUpdated(toolPart(ids[3]!, tools[3]!, "completed", inputs[3]!)), 210) + await timeline.send( + partUpdated(toolPart(ids[0]!, tools[0]!, "error", inputs[0]!, { error: "Read interrupted" })), + 110, + ) + await timeline.send(partUpdated(toolPart(ids[2]!, tools[2]!, "completed", inputs[2]!)), 250) + await expect(group.locator('[data-component="tool-status-title"]')).toHaveAttribute("aria-label", "Explored") + await timeline.send(status("idle"), 700) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "mixed-context", + trace, + visualPlan(regions, [ + { type: "required", regions: ["context", "following"] }, + { type: "unique", regions: ["context"] }, + { type: "stable", regions: ["context"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxReversals: 4 }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["context", "following"] }, + ]), + ) + await expect(group.locator('[data-component="tool-status-title"]')).toHaveAttribute("aria-label", "Explored") + await expect(group.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true") + await group.locator('[data-slot="collapsible-trigger"]').click() + await expect(group.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "false") + await timeline.send(partUpdated(textPart("prt_ctx_late_sibling", "Later sibling content")), 200) + await expect(group.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "false") + await group.locator('[data-slot="collapsible-trigger"]').click() + await expect(group.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true") + }) +}) + +function questionInput() { + return { questions: [{ header: "Stability", question: "Keep it stable?", options: [] }] } +} + +function toolRegion(id: string) { + return { selector: `[data-timeline-part-id="${id}"]`, closest: '[data-timeline-row="AssistantPart"]' } +} diff --git a/packages/app/e2e/performance/timeline-stability/transition-matrix.spec.ts b/packages/app/e2e/performance/timeline-stability/transition-matrix.spec.ts new file mode 100644 index 000000000000..e999e8c50a6f --- /dev/null +++ b/packages/app/e2e/performance/timeline-stability/transition-matrix.spec.ts @@ -0,0 +1,272 @@ +import { expect, test } from "@playwright/test" +import { + defineVisualRegions, + reportVisualStability, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../../utils/visual-stability" +import { + assistantID, + assistantMessage, + completedAssistantInfo, + event, + messageUpdated, + partDelta, + partUpdated, + setupTimeline, + shell, + status, + textPart, + toolPart, + userMessage, +} from "./fixture" + +test("keeps unchanged siblings stable while a middle part is inserted and removed", async ({ page }, testInfo) => { + const firstID = "prt_mutation_01_first" + const middleID = "prt_mutation_02_middle" + const lastID = "prt_mutation_03_last" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([textPart(firstID, "First stable row"), textPart(lastID, "Last stable row")], { + completed: false, + }), + ], + cpuRate: 4, + }) + const regions = defineVisualRegions({ + first: { selector: `[data-timeline-part-id="${firstID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + last: { selector: `[data-timeline-part-id="${lastID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partUpdated(textPart(middleID, "Inserted middle row. ".repeat(12))), 350) + await expect(page.locator(`[data-timeline-part-id="${middleID}"]`)).toBeVisible() + await timeline.send( + event("message.part.removed", { sessionID: "ses_timeline_stability", messageID: assistantID, partID: middleID }), + 500, + ) + await expect(page.locator(`[data-timeline-part-id="${middleID}"]`)).toHaveCount(0) + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, "middle-insert-remove", trace, stablePairPlan(regions, 1)) +}) + +test("streams text through growth, canonical replacement, and completion", async ({ page }, testInfo) => { + const textID = "prt_text_reconcile" + const followingID = "prt_text_reconcile_following" + const assistant = assistantMessage([textPart(textID, "Starting"), textPart(followingID, "Following text row")], { + completed: false, + }) + const timeline = await setupTimeline(page, { messages: [userMessage(), assistant], cpuRate: 4 }) + const regions = defineVisualRegions({ + text: { selector: `[data-timeline-part-id="${textID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send(partDelta(textID, " streamed content"), 100) + await timeline.send(partDelta(textID, "\n\n- item one\n- item two\n- item three"), 180) + await timeline.send(partUpdated(textPart(textID, "Canonical replacement with a shorter final paragraph.")), 200) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "text-reconcile", + trace, + visualPlan(regions, [ + { type: "required", regions: ["text", "following"] }, + { type: "unique", regions: ["text", "following"] }, + { type: "stable", regions: ["text", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 1, maxReversals: 2 }, + { type: "label-stability", regions: "all" }, + { type: "preserve-bottom-anchor" }, + { type: "flow", regions: ["text", "following"] }, + ]), + ) +}) + +test("inserts a completed question between stable rows", async ({ page }, testInfo) => { + const firstID = "prt_question_01_first" + const questionID = "prt_question_02_hidden" + const lastID = "prt_question_03_last" + const input = { questions: [{ header: "Choice", question: "Keep stable?", options: [] }] } + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [ + textPart(firstID, "Before question"), + toolPart(questionID, "question", "running", input), + textPart(lastID, "After question"), + ], + { completed: false }, + ), + ], + cpuRate: 4, + }) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0) + const regions = defineVisualRegions({ + first: { selector: `[data-timeline-part-id="${firstID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + last: { selector: `[data-timeline-part-id="${lastID}"]`, closest: '[data-timeline-row="AssistantPart"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + partUpdated(toolPart(questionID, "question", "completed", input, { metadata: { answers: [["Yes"]] } })), + 600, + ) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toBeVisible() + const trace = await stopVisualProbe(page) + await reportVisualStability(testInfo, "question-insert", trace, stablePairPlan(regions, 0)) +}) + +test("replaces thinking with an assistant error without a blank turn", async ({ page }, testInfo) => { + const assistant = assistantMessage([], { completed: false }) + const timeline = await setupTimeline(page, { messages: [userMessage(), assistant], cpuRate: 4 }) + await timeline.send(status("busy"), 150) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + const regions = defineVisualRegions({ + thinking: { selector: '[data-timeline-row="Thinking"]' }, + error: { selector: '[data-timeline-row="Error"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + messageUpdated({ + ...assistant.info, + error: { name: "APIError", data: { message: "Provider failed visibly", isRetryable: false } }, + }), + 500, + ) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-row="Error"]')).toContainText("Provider failed visibly") + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "thinking-error", + trace, + visualPlan(regions, [ + { type: "required", regions: ["thinking", "error"] }, + { type: "continuous-any", regions: ["thinking", "error"] }, + { type: "unique", regions: ["thinking", "error"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all" }, + { type: "label-stability", regions: "all" }, + ]), + ) +}) + +test("updates retry attempts and long provider messages without remounting the retry row", async ({ + page, +}, testInfo) => { + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([], { completed: false })], + cpuRate: 4, + }) + await timeline.send(status("retry", 1), 120) + await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible() + const regions = defineVisualRegions({ + retry: { selector: '[data-timeline-row="Retry"]' }, + }) + await startVisualProbe(page, regions) + await timeline.send( + event("session.status", { + sessionID: "ses_timeline_stability", + status: { + type: "retry", + attempt: 2, + message: "A very long provider retry message ".repeat(8), + next: Date.now() + 10_000, + }, + }), + 300, + ) + await timeline.send(status("retry", 3), 300) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "retry-evolution", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["retry"] }, + { type: "unique", regions: ["retry"] }, + { type: "stable", regions: ["retry"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + ], + { perMarker: true }, + ), + ) +}) + +test("reducer-hardening: removes a historical turn one message at a time without moving a visible lower anchor twice", async ({ + page, +}, testInfo) => { + const removeUserID = "msg_0500_remove_user" + const removeAssistantID = "msg_0501_remove_assistant" + const anchorUserID = "msg_2000_anchor_user" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(undefined, { id: removeUserID, created: 1690000000000 }), + assistantMessage([textPart("prt_remove_text", "Removed historical content. ".repeat(15))], { + id: removeAssistantID, + parentID: removeUserID, + created: 1690000001000, + }), + userMessage(undefined, { id: anchorUserID, created: 1700000000000 }), + assistantMessage([textPart("prt_anchor_text", "Visible anchor response")], { + id: "msg_2001_anchor_assistant", + parentID: anchorUserID, + created: 1700000001000, + }), + ], + cpuRate: 4, + }) + const regions = defineVisualRegions({ + anchor: { selector: `[data-timeline-row="UserMessage"][data-message-id="${anchorUserID}"]` }, + }) + await startVisualProbe(page, regions) + await timeline.send( + event("message.removed", { sessionID: "ses_timeline_stability", messageID: removeAssistantID }), + 200, + ) + await timeline.send(event("message.removed", { sessionID: "ses_timeline_stability", messageID: removeUserID }), 500) + const trace = await stopVisualProbe(page) + await reportVisualStability( + testInfo, + "historical-turn-remove", + trace, + visualPlan( + regions, + [ + { type: "required", regions: ["anchor"] }, + { type: "unique", regions: ["anchor"] }, + { type: "stable", regions: ["anchor"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals: 0 }, + { type: "label-stability", regions: "all" }, + ], + { perMarker: true }, + ), + ) +}) + +function stablePairPlan( + regions: Record<"first" | "last", { selector: string; closest?: string }>, + maxPositionReversals: number, +) { + return visualPlan(regions, [ + { type: "required", regions: ["first", "last"] }, + { type: "unique", regions: ["first", "last"] }, + { type: "stable", regions: ["first", "last"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all", maxPositionReversals }, + { type: "label-stability", regions: "all" }, + ]) +} diff --git a/packages/app/e2e/performance/timeline/review-pane-scaling-benchmark.spec.ts b/packages/app/e2e/performance/timeline/review-pane-scaling-benchmark.spec.ts new file mode 100644 index 000000000000..81de0a055f90 --- /dev/null +++ b/packages/app/e2e/performance/timeline/review-pane-scaling-benchmark.spec.ts @@ -0,0 +1,312 @@ +import type { Page } from "@playwright/test" +import { benchmark, expect } from "../benchmark" +import { setupTimelineBenchmark } from "./session-timeline-benchmark.fixture" + +const changedLinesPerFile = 100 +const linesPerSide = changedLinesPerFile / 2 +const fileCounts = [1, 10, 100, 1_000, 10_000] +const filesPerDirectory = 100 +const readyFrames = 3 +const completionTimeoutMs = Number(process.env.REVIEW_PANE_COMPLETION_TIMEOUT_MS ?? 900_000) + +type ReviewPaneScalingSample = { + observedAtMs: number + logicalRows: number + treeRows: number + fileRows: number + diffLines: number + header: string + ready: boolean +} + +type ReviewPaneScalingProbe = { + startedAt?: number + firstTreeRowMs?: number + logicalTreeReadyMs?: number + firstDiffRenderMs?: number + stableReadyMs?: number + samples: ReviewPaneScalingSample[] + frameTimesMs: number[] + longTasks: { startTime: number; duration: number }[] + stop: () => void +} + +benchmark.describe("performance: review pane scaling", () => { + for (const fileCount of fileCounts) { + const changedLines = fileCount * changedLinesPerFile + + benchmark( + `${changedLines} changed lines across ${fileCount} ${fileCount === 1 ? "file" : "files"}`, + async ({ page, report }) => { + benchmark.setTimeout(1_200_000) + await page.emulateMedia({ reducedMotion: "reduce" }) + + const patchByteLimit = Number(process.env.REVIEW_PANE_PATCH_BYTE_LIMIT ?? Number.POSITIVE_INFINITY) + if (Number.isNaN(patchByteLimit) || patchByteLimit < 0) + throw new Error(`Invalid REVIEW_PANE_PATCH_BYTE_LIMIT: ${process.env.REVIEW_PANE_PATCH_BYTE_LIMIT}`) + const responseBody = JSON.stringify(createScalingDiffs(fileCount, patchByteLimit)) + await setupTimelineBenchmark(page, { + historyTurns: 0, + eventBatch: 1, + newLayoutDesigns: true, + }) + await page.route("**/vcs/diff**", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: responseBody, + }), + ) + + const expectedRows = fileCount + 2 + Math.ceil(fileCount / filesPerDirectory) + const metrics = await measureReviewPaneLoad(page, { + expectedFile: reviewFile(0), + expectedRows, + }) + const search = await measureBroadReviewSearch(page, fileCount) + + expect(metrics.logicalRows).toBe(expectedRows) + expect(metrics.fileRows).toBeGreaterThan(0) + expect(metrics.treeRows).toBeGreaterThan(0) + expect(metrics.diffLines).toBeGreaterThan(0) + expect(search.logicalRows).toBe(fileCount) + expect(search.renderedRows).toBeGreaterThan(0) + report( + { ...metrics, search }, + { + fileCount, + changedLinesPerFile, + changedLines, + additions: changedLines / 2, + deletions: changedLines / 2, + patchLines: changedLines, + patchByteLimit: Number.isFinite(patchByteLimit) ? patchByteLimit : null, + payloadBytes: new TextEncoder().encode(responseBody).byteLength, + expectedRows, + }, + ) + }, + ) + } +}) + +async function measureBroadReviewSearch(page: Page, expectedRows: number) { + const filter = page.getByRole("searchbox", { name: "Filter files" }) + await filter.evaluate((element) => { + element.addEventListener( + "input", + () => { + ;(window as Window & { __reviewSearchStartedAt?: number }).__reviewSearchStartedAt = performance.now() + }, + { once: true, capture: true }, + ) + }) + await filter.fill("file-") + + return page.evaluate((expectedRows) => { + const startedAt = (window as Window & { __reviewSearchStartedAt?: number }).__reviewSearchStartedAt! + return new Promise<{ stableMs: number; logicalRows: number; renderedRows: number }>((resolve) => { + let previous = -1 + let streak = 0 + const sample = () => { + const tree = document.querySelector('#review-panel [data-component="file-tree-v2"]') + const rows = [...document.querySelectorAll('#review-panel [data-slot="file-tree-v2-row"]')] + const logicalRows = Number(tree?.dataset.totalRows ?? rows.length) + const ready = + logicalRows === expectedRows && rows.length > 0 && rows.every((row) => row.textContent?.includes("file-")) + streak = ready && rows.length === previous ? streak + 1 : ready ? 1 : 0 + previous = rows.length + if (streak >= 3) { + resolve({ stableMs: performance.now() - startedAt, logicalRows, renderedRows: rows.length }) + return + } + requestAnimationFrame(sample) + } + requestAnimationFrame(sample) + }) + }, expectedRows) +} + +function createScalingDiffs(fileCount: number, patchByteLimit: number) { + const changes = Array.from({ length: linesPerSide }, (_, index) => { + const line = String(index).padStart(3, "0") + return `-export const value_${line} = "before"\n+export const value_${line} = "after"` + }).join("\n") + let patchBytes = 0 + let capped = false + + return Array.from({ length: fileCount }, (_, index) => { + const file = reviewFile(index) + const fullPatch = [ + `diff --git a/${file} b/${file}`, + `--- a/${file}`, + `+++ b/${file}`, + `@@ -1,${linesPerSide} +1,${linesPerSide} @@`, + changes, + ].join("\n") + if (index === 0 && fullPatch.length > patchByteLimit) + throw new Error(`REVIEW_PANE_PATCH_BYTE_LIMIT must include the active patch (${fullPatch.length} bytes)`) + const patch = !capped && patchBytes + fullPatch.length <= patchByteLimit ? fullPatch : emptyReviewPatch(file) + if (patch === fullPatch) patchBytes += fullPatch.length + else capped = true + return { + file, + patch, + additions: linesPerSide, + deletions: linesPerSide, + status: "modified" as const, + } + }) +} + +function emptyReviewPatch(file: string) { + return [`diff --git a/${file} b/${file}`, `--- a/${file}`, `+++ b/${file}`].join("\n") +} + +function reviewFile(index: number) { + return `src/review/d${String(Math.floor(index / filesPerDirectory)).padStart(5, "0")}/file-${String(index).padStart(5, "0")}.ts` +} + +async function measureReviewPaneLoad(page: Page, input: { expectedFile: string; expectedRows: number }) { + const toggle = page.getByRole("button", { name: "Toggle review" }) + await expect(toggle).toBeVisible() + await toggle.evaluate((element) => element.setAttribute("data-review-pane-scaling-toggle", "")) + await installReviewPaneScalingProbe(page, input) + await toggle.click() + await page.waitForFunction( + () => + (window as Window & { __reviewPaneScalingProbe?: ReviewPaneScalingProbe }).__reviewPaneScalingProbe + ?.stableReadyMs !== undefined, + undefined, + { timeout: completionTimeoutMs }, + ) + + return page.evaluate(() => { + const probe = (window as Window & { __reviewPaneScalingProbe?: ReviewPaneScalingProbe }).__reviewPaneScalingProbe! + probe.stop() + const startedAt = probe.startedAt! + const final = probe.samples.at(-1)! + const resources = performance + .getEntriesByType("resource") + .filter((entry) => entry.name.includes("/vcs/diff")) as PerformanceResourceTiming[] + const resource = resources.at(-1) + const longTasks = probe.longTasks.filter( + (entry) => entry.startTime >= startedAt && entry.startTime <= startedAt + probe.stableReadyMs!, + ) + const frameGaps = probe.frameTimesMs.map((time, index) => time - (probe.frameTimesMs[index - 1] ?? 0)) + + return { + firstTreeRowMs: probe.firstTreeRowMs ?? null, + logicalTreeReadyMs: probe.logicalTreeReadyMs ?? null, + firstDiffRenderMs: probe.firstDiffRenderMs ?? null, + stableReadyMs: probe.stableReadyMs ?? null, + responseStartMs: resource ? resource.responseStart - startedAt : null, + responseEndMs: resource ? resource.responseEnd - startedAt : null, + responseToStableMs: resource ? probe.stableReadyMs! - (resource.responseEnd - startedAt) : null, + treeRows: final.treeRows, + logicalRows: final.logicalRows, + fileRows: final.fileRows, + diffLines: final.diffLines, + samples: probe.samples.length, + maxFrameGapMs: Math.max(0, ...frameGaps), + longTaskCount: longTasks.length, + longTaskTotalMs: longTasks.reduce((sum, entry) => sum + entry.duration, 0), + maxLongTaskMs: Math.max(0, ...longTasks.map((entry) => entry.duration)), + } + }) +} + +async function installReviewPaneScalingProbe(page: Page, input: { expectedFile: string; expectedRows: number }) { + await page.evaluate( + ({ expectedFile, expectedRows, stableFrames }) => { + let running = true + let readyStreak = 0 + const basename = expectedFile.split("/").at(-1)! + const longTaskObserver = PerformanceObserver.supportedEntryTypes.includes("longtask") + ? new PerformanceObserver((list) => { + probe.longTasks.push( + ...list.getEntries().map((entry) => ({ startTime: entry.startTime, duration: entry.duration })), + ) + }) + : undefined + const probe: ReviewPaneScalingProbe = { + samples: [], + frameTimesMs: [], + longTasks: [], + stop: () => { + running = false + longTaskObserver?.disconnect() + }, + } + + const sample = (time: number) => { + if (!running || probe.startedAt === undefined) return + const panel = document.querySelector("#review-panel") + const tree = panel?.querySelector('[data-component="file-tree-v2"]') + const rows = panel?.querySelectorAll('[data-slot="file-tree-v2-row"]') ?? [] + const fileRows = panel?.querySelectorAll('button[data-slot="file-tree-v2-row"]') ?? [] + const header = + panel?.querySelector('[data-slot="session-review-v2-file-header"]')?.textContent?.trim() ?? "" + const viewers = panel + ? [...panel.querySelectorAll('[data-component="file"][data-mode="diff"]')] + : [] + const diffLines = viewers.reduce( + (sum, viewer) => + sum + (viewer.querySelector("diffs-container")?.shadowRoot?.querySelectorAll("[data-line]").length ?? 0), + 0, + ) + const observedAtMs = time - probe.startedAt + const logicalRows = Number(tree?.dataset.totalRows ?? rows.length) + const ready = + logicalRows === expectedRows && + fileRows.length > 0 && + header.includes(basename) && + viewers.length === 1 && + diffLines > 0 + const previous = probe.samples.at(-1) + const stable = + ready && + previous?.ready === true && + previous.logicalRows === logicalRows && + previous.treeRows === rows.length && + previous.fileRows === fileRows.length && + previous.diffLines === diffLines && + previous.header === header + + probe.frameTimesMs.push(observedAtMs) + probe.samples.push({ + observedAtMs, + logicalRows, + treeRows: rows.length, + fileRows: fileRows.length, + diffLines, + header, + ready, + }) + if (probe.firstTreeRowMs === undefined && rows.length > 0) probe.firstTreeRowMs = observedAtMs + if (probe.logicalTreeReadyMs === undefined && logicalRows === expectedRows) + probe.logicalTreeReadyMs = observedAtMs + if (probe.firstDiffRenderMs === undefined && diffLines > 0) probe.firstDiffRenderMs = observedAtMs + readyStreak = !ready ? 0 : stable ? readyStreak + 1 : 1 + if (readyStreak === stableFrames) probe.stableReadyMs = observedAtMs + if (probe.stableReadyMs === undefined) requestAnimationFrame(sample) + } + + longTaskObserver?.observe({ type: "longtask", buffered: true }) + document.addEventListener( + "click", + (event) => { + const toggle = event.target instanceof Element ? event.target.closest("button") : undefined + if (!toggle?.hasAttribute("data-review-pane-scaling-toggle")) return + probe.startedAt = performance.now() + performance.mark("opencode.review-pane-scaling.click") + requestAnimationFrame(sample) + }, + { capture: true, once: true }, + ) + ;(window as Window & { __reviewPaneScalingProbe?: ReviewPaneScalingProbe }).__reviewPaneScalingProbe = probe + }, + { ...input, stableFrames: readyFrames }, + ) +} diff --git a/packages/app/e2e/performance/timeline/session-parent-hydration-benchmark.spec.ts b/packages/app/e2e/performance/timeline/session-parent-hydration-benchmark.spec.ts new file mode 100644 index 000000000000..2a214831daa8 --- /dev/null +++ b/packages/app/e2e/performance/timeline/session-parent-hydration-benchmark.spec.ts @@ -0,0 +1,151 @@ +import type { Page } from "@playwright/test" +import { expectSessionTitle } from "../../utils/waits" +import { mockOpenCodeServer } from "../../utils/mock-server" +import { benchmark, expect, withBenchmarkPage } from "../benchmark" +import { fixture } from "./session-timeline-stress.fixture" +import { installStressSessionTabs, stressSessionHref } from "./timeline-test-helpers" +import { measureSessionSwitch, waitForStableTimeline } from "./session-tab-switch-probe" + +type ParentHydrationBenchmarkMode = "natural" | "candidate" + +const mode = process.env.SESSION_PARENT_HYDRATION_BENCHMARK_MODE ?? "natural" +if (mode !== "natural" && mode !== "candidate") throw new Error(`Unknown parent hydration benchmark mode: ${mode}`) +const userID = "msg_parent_hydration_user" +const user = { + ...fixture.messages[fixture.targetID][0]!, + info: { ...fixture.messages[fixture.targetID][0]!.info, id: userID, time: { created: 1700001000000 } }, + parts: fixture.messages[fixture.targetID][0]!.parts.map((part, index) => ({ + ...part, + id: `prt_parent_hydration_user_${index}`, + messageID: userID, + })), +} +const assistantSeed = fixture.messages[fixture.targetID][3]! +const assistants = Array.from({ length: 14 }, (_, index) => { + const messageID = `msg_parent_hydration_${String(index).padStart(2, "0")}` + return { + ...assistantSeed, + info: { + ...assistantSeed.info, + id: messageID, + parentID: userID, + time: { created: 1700001001000 + index * 1_000, completed: 1700001001500 + index * 1_000 }, + }, + parts: assistantSeed.parts.map((part, partIndex) => ({ + ...part, + id: `prt_parent_hydration_${String(index).padStart(2, "0")}_${partIndex}`, + messageID, + })), + } +}) +const messages = [user, ...assistants] +const target = fixture.sessions.find((session) => session.id === fixture.targetID)! +const lastID = userID +const lastPartID = assistants.at(-1)!.parts.at(-1)!.id + +benchmark("hydrates an orphaned latest turn after a cold session click", async ({ browser, report }, testInfo) => { + benchmark.setTimeout(180_000) + const results = [] as Awaited>[] + for (let run = 0; run < 5; run++) { + results.push( + await withBenchmarkPage( + browser, + `session-parent-hydration-${mode}-${run}`, + (page) => trial(page, mode), + testInfo, + ), + ) + } + const timing = results.map((result) => result.metrics.firstCorrectObservedMs!).sort((a, b) => a - b) + report( + { + results: results.map((result) => ({ ...result.metrics, historyGateCount: result.historyGateCount })), + summary: { + firstCorrectObservedMs: { min: timing[0], median: timing[2], max: timing.at(-1) }, + blankSamples: results.map((result) => result.metrics.blankSamples), + requestCounts: { + list: results.map((result) => result.requestCounts.list), + parent: results.map((result) => result.requestCounts.parent), + }, + historyGateCount: results.map((result) => result.historyGateCount), + }, + }, + { mode }, + ) +}) + +async function trial(page: Page, mode: ParentHydrationBenchmarkMode) { + const requests: { type: "list" | "parent"; before?: string }[] = [] + const history = mode === "candidate" ? Promise.withResolvers() : undefined + let historyGates = 0 + await mockOpenCodeServer(page, { + sessions: fixture.sessions.filter((session) => session.id === fixture.sourceID), + provider: fixture.provider, + directory: fixture.directory, + project: fixture.project, + messageDelay: 50, + onMessages: (request) => { + if (request.sessionID === fixture.targetID && request.phase === "start") + requests.push({ type: "list", before: request.before }) + }, + beforeMessagesResponse: (request) => { + if (mode !== "candidate" || request.sessionID !== fixture.targetID || !request.before) return Promise.resolve() + historyGates++ + return history!.promise + }, + onMessage: (request) => { + if (request.sessionID === fixture.targetID && request.messageID === userID) requests.push({ type: "parent" }) + }, + message: (sessionID, messageID) => { + if (sessionID !== fixture.targetID || messageID !== userID) return + return user + }, + pageMessages: (sessionID, limit, before) => { + const items = sessionID === fixture.targetID ? messages : fixture.messages[fixture.sourceID] + const end = before ? items.findIndex((message) => message.info.id === before) : items.length + const start = Math.max(0, end - limit) + return { items: items.slice(start, end), cursor: start > 0 ? items[start]!.info.id : undefined } + }, + }) + await page.route(`**/session/${fixture.targetID}`, (route) => + route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(target) }), + ) + await installStressSessionTabs(page, { sessionIDs: [fixture.sourceID] }) + await page.goto(stressSessionHref(fixture.sourceID)) + await expectSessionTitle(page, fixture.expected.sourceTitle) + await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!) + + const href = stressSessionHref(fixture.targetID) + await page.evaluate( + ({ href, title }) => { + const link = document.createElement("a") + link.id = "parent-hydration-target" + link.href = href + link.textContent = title + document.body.append(link) + }, + { href, title: target.title }, + ) + const metrics = await measureSessionSwitch(page, { + destinationIDs: messages.map((message) => message.info.id), + sourceIDs: fixture.messages[fixture.sourceID].map((message) => message.info.id), + lastID, + requiredPartID: lastPartID, + requireBottomAnchor: false, + href, + switch: async () => { + await page.locator("#parent-hydration-target").click() + await expectSessionTitle(page, target.title) + }, + }).finally(() => history?.resolve()) + expect(metrics.firstCorrectObservedMs).not.toBeNull() + const requestCounts = { + list: requests.filter((request) => request.type === "list").length, + parent: requests.filter((request) => request.type === "parent").length, + } + if (mode === "candidate") { + expect(requestCounts.parent).toBe(1) + expect(historyGates).toBe(1) + } + return { metrics, requestCounts, historyGateCount: historyGates } +} diff --git a/packages/app/e2e/performance/timeline/session-tab-switch-benchmark.spec.ts b/packages/app/e2e/performance/timeline/session-tab-switch-benchmark.spec.ts index 2e80d703813e..48d4d3deb0d0 100644 --- a/packages/app/e2e/performance/timeline/session-tab-switch-benchmark.spec.ts +++ b/packages/app/e2e/performance/timeline/session-tab-switch-benchmark.spec.ts @@ -2,7 +2,13 @@ import type { Page } from "@playwright/test" import { expectSessionTitle } from "../../utils/waits" import { benchmark, expect, withBenchmarkPage } from "../benchmark" import { fixture } from "./session-timeline-stress.fixture" -import { installStressSessionTabs, mockStressTimeline, stressSessionHref } from "./timeline-test-helpers" +import { + createReviewDiffs, + installStressSessionTabs, + installTimelineSettings, + mockStressTimeline, + stressSessionHref, +} from "./timeline-test-helpers" import { measureSessionSwitch, waitForStableTimeline } from "./session-tab-switch-probe" type Result = Awaited> @@ -20,8 +26,41 @@ benchmark("benchmarks cold and hot session tab switching", async ({ browser, rep report({ results, summary: summarize(results) }) }) -async function trial(page: Page, mode: "cold" | "hot") { - await mockStressTimeline(page) +benchmark( + "benchmarks v2 session tab switching with and without the review pane", + async ({ browser, report }, testInfo) => { + benchmark.setTimeout(360_000) + const runs = Number(process.env.SESSION_TAB_SWITCH_RUNS ?? 5) + const results = { + closed: { cold: [] as Result[], hot: [] as Result[] }, + open: { cold: [] as Result[], hot: [] as Result[] }, + } + for (const reviewPane of ["closed", "open"] as const) { + for (const mode of ["cold", "hot"] as const) { + for (let run = 0; run < runs; run++) { + results[reviewPane][mode].push( + await withBenchmarkPage( + browser, + `session-tab-switch-v2-${reviewPane}-${mode}-${run}`, + (page) => trial(page, mode, { newLayoutDesigns: true, reviewPane }), + testInfo, + ), + ) + } + } + } + report({ results, summary: summarizeReviewPane(results) }, { runs, reviewDiffs: createReviewDiffs().length }) + }, +) + +async function trial( + page: Page, + mode: "cold" | "hot", + options?: { newLayoutDesigns?: boolean; reviewPane?: "closed" | "open" }, +) { + const reviewDiffs = options?.newLayoutDesigns ? createReviewDiffs() : undefined + await mockStressTimeline(page, { vcsDiff: reviewDiffs }) + if (options?.newLayoutDesigns) await installTimelineSettings(page) await installStressSessionTabs(page) if (mode === "hot") { await page.goto(stressSessionHref(fixture.targetID)) @@ -33,6 +72,10 @@ async function trial(page: Page, mode: "cold" | "hot") { await expectSessionTitle(page, fixture.expected.sourceTitle) } await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!) + if (options?.reviewPane === "open") { + await openReviewPane(page) + await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!) + } const destinationIDs = fixture.messages[fixture.targetID].map((message) => message.info.id) const sourceIDs = fixture.messages[fixture.sourceID].map((message) => message.info.id) @@ -70,6 +113,15 @@ function summarize(results: Record<"cold" | "hot", Result[]>) { ) } +function summarizeReviewPane(results: Record<"closed" | "open", Record<"cold" | "hot", Result[]>>) { + return Object.fromEntries( + Object.entries(results).map(([reviewPane, values]) => [ + reviewPane, + summarize(values as Record<"cold" | "hot", Result[]>), + ]), + ) +} + async function switchSession(page: Page, sessionID: string, title: string) { const href = stressSessionHref(sessionID) const tab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`).first() @@ -77,3 +129,16 @@ async function switchSession(page: Page, sessionID: string, title: string) { await tab.click() await expectSessionTitle(page, title) } + +async function openReviewPane(page: Page) { + await page.getByRole("button", { name: "Toggle review" }).click() + const panel = page.locator("#review-panel") + await expect(panel).toBeVisible() + // Text-based readiness works across review implementations; the legacy list mounts + // diff viewers lazily while V2 mounts the active preview eagerly. + await page.waitForFunction(() => { + const panel = document.querySelector("#review-panel") + const text = panel?.textContent ?? "" + return text.includes("generated-000.ts") && text.includes("+3") + }) +} diff --git a/packages/app/e2e/performance/timeline/session-tab-switch-metrics.ts b/packages/app/e2e/performance/timeline/session-tab-switch-metrics.ts index e315c2ad43b9..5298b9b0b7df 100644 --- a/packages/app/e2e/performance/timeline/session-tab-switch-metrics.ts +++ b/packages/app/e2e/performance/timeline/session-tab-switch-metrics.ts @@ -4,7 +4,15 @@ export type SessionSwitchSample = { source: string[] hasVisibleRows: boolean last: boolean + requiredPartVisible?: boolean + bottomAnchorRequired?: boolean bottomErrorPx?: number + review?: { + fileHost: boolean + fileHostReplaced: boolean + header: string + replacedLevels: string[] + } } export function classifySessionSwitch(samples: SessionSwitchSample[]) { @@ -23,6 +31,10 @@ export function classifySessionSwitch(samples: SessionSwitchSample[]) { (sample) => sample.hasVisibleRows && sample.destination.length === 0 && sample.source.length === 0, ).length, sourceSamples: samples.filter((sample) => sample.source.length > 0).length, + reviewFileHostMissingSamples: samples.filter((sample) => sample.review && !sample.review.fileHost).length, + reviewFileHostReplacedSamples: samples.filter((sample) => sample.review?.fileHostReplaced).length, + reviewHeaders: [...new Set(samples.flatMap((sample) => (sample.review ? [sample.review.header] : [])))], + reviewReplacedLevels: [...new Set(samples.flatMap((sample) => sample.review?.replacedLevels ?? []))], } } @@ -31,7 +43,8 @@ export function isCorrectDestination(sample: SessionSwitchSample) { sample.destination.length > 0 && sample.source.length === 0 && sample.last && - Math.abs(sample.bottomErrorPx ?? Infinity) <= 1 + sample.requiredPartVisible !== false && + (sample.bottomAnchorRequired === false || Math.abs(sample.bottomErrorPx ?? Infinity) <= 1) ) } diff --git a/packages/app/e2e/performance/timeline/session-tab-switch-probe.ts b/packages/app/e2e/performance/timeline/session-tab-switch-probe.ts index 14f9d2d003e4..955da80367d7 100644 --- a/packages/app/e2e/performance/timeline/session-tab-switch-probe.ts +++ b/packages/app/e2e/performance/timeline/session-tab-switch-probe.ts @@ -8,19 +8,56 @@ type SessionSwitchProbe = { async function installSessionSwitchProbe( page: Page, - input: { destinationIDs: string[]; sourceIDs: string[]; lastID: string; href: string }, + input: { + destinationIDs: string[] + sourceIDs: string[] + lastID: string + requiredPartID?: string + requireBottomAnchor?: boolean + href: string + }, ) { - await page.evaluate(({ destinationIDs, sourceIDs, lastID, href }) => { + await page.evaluate(({ destinationIDs, sourceIDs, lastID, requiredPartID, requireBottomAnchor, href }) => { const destination = new Set(destinationIDs) const source = new Set(sourceIDs) const samples: SessionSwitchSample[] = [] let started: number | undefined let running = true + const reviewLevels: Record = { + panel: "#review-panel", + tabs: '#review-panel [data-component="tabs"]', + body: '#review-panel [data-slot="session-review-v2-body"]', + review: '#review-panel [data-component="session-review-v2"]', + preview: '#review-panel [data-slot="session-review-v2-preview"]', + scroll: '#review-panel [data-slot="session-review-v2-diff-scroll"]', + file: '#review-panel [data-component="file"][data-mode="diff"]', + } + const initialReviewNodes: Record = {} const sample = () => { if (!running || started === undefined) return setTimeout(() => { if (!running || started === undefined) return const observedAtMs = performance.now() - started + const reviewPanel = document.querySelector("#review-panel") + const reviewFile = reviewPanel?.querySelector('[data-component="file"][data-mode="diff"]') + const initialReviewFile = initialReviewNodes.file + const replacedLevels = Object.entries(reviewLevels).flatMap(([name, selector]) => { + const initial = initialReviewNodes[name] + if (!initial) return [] + const current = document.querySelector(selector) + return current && current !== initial ? [name] : [] + }) + const review = reviewPanel + ? { + fileHost: !!reviewFile, + fileHostReplaced: !!initialReviewFile && !!reviewFile && reviewFile !== initialReviewFile, + header: + reviewPanel + .querySelector('[data-slot="session-review-v2-file-header"]') + ?.textContent?.trim() ?? "", + replacedLevels, + } + : undefined const root = [...document.querySelectorAll(".scroll-view__viewport")].find((element) => element.querySelector("[data-timeline-row]"), ) @@ -36,6 +73,13 @@ async function installSessionSwitchProbe( const rect = element.getBoundingClientRect() return rect.bottom > view.top && rect.top < view.bottom }) + const requiredPartVisible = requiredPartID + ? [...root.querySelectorAll("[data-timeline-part-id]")].some((element) => { + if (element.dataset.timelinePartId !== requiredPartID) return false + const rect = element.getBoundingClientRect() + return rect.width > 0 && rect.height > 0 && rect.bottom > view.top && rect.top < view.bottom + }) + : undefined const spacer = root.querySelector('[data-timeline-row="bottom-spacer"]')?.getBoundingClientRect() samples.push({ observedAtMs, @@ -43,10 +87,22 @@ async function installSessionSwitchProbe( source: visible.filter((id) => source.has(id)), hasVisibleRows, last: visible.includes(lastID), + requiredPartVisible, + bottomAnchorRequired: requireBottomAnchor !== false, bottomErrorPx: spacer ? spacer.bottom - view.bottom : undefined, + review, }) } else { - samples.push({ observedAtMs, destination: [], source: [], hasVisibleRows: false, last: false }) + samples.push({ + observedAtMs, + destination: [], + source: [], + hasVisibleRows: false, + last: false, + requiredPartVisible: requiredPartID ? false : undefined, + bottomAnchorRequired: requireBottomAnchor !== false, + review, + }) } requestAnimationFrame(sample) }, 0) @@ -57,6 +113,9 @@ async function installSessionSwitchProbe( const link = event.target instanceof Element ? event.target.closest("a") : undefined if (link?.getAttribute("href") !== href) return started = performance.now() + for (const [name, selector] of Object.entries(reviewLevels)) { + initialReviewNodes[name] = document.querySelector(selector) + } requestAnimationFrame(sample) }, { capture: true, once: true }, @@ -83,7 +142,8 @@ async function waitForStableSessionSwitch(page: Page) { sample.destination.length > 0 && sample.source.length === 0 && sample.last && - Math.abs(sample.bottomErrorPx ?? Infinity) <= 1, + sample.requiredPartVisible !== false && + (sample.bottomAnchorRequired === false || Math.abs(sample.bottomErrorPx ?? Infinity) <= 1), ) ) }) @@ -101,13 +161,27 @@ async function collectSessionSwitchResult(page: Page) { export async function measureSessionSwitch( page: Page, - input: { destinationIDs: string[]; sourceIDs: string[]; lastID: string; href: string; switch: () => Promise }, + input: { + destinationIDs: string[] + sourceIDs: string[] + lastID: string + requiredPartID?: string + requireBottomAnchor?: boolean + href: string + switch: () => Promise + }, ) { const { switch: run, ...probe } = input await installSessionSwitchProbe(page, probe) - await run() - await waitForStableSessionSwitch(page) - return collectSessionSwitchResult(page) + try { + await run() + await waitForStableSessionSwitch(page) + return await collectSessionSwitchResult(page) + } finally { + await page.evaluate(() => { + ;(window as Window & { __sessionSwitchProbe?: SessionSwitchProbe }).__sessionSwitchProbe?.stop() + }) + } } export async function waitForStableTimeline(page: Page, lastID: string) { diff --git a/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts b/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts index 4dad1df37b62..a22d5cc3317d 100644 --- a/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts +++ b/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts @@ -93,36 +93,53 @@ const assistantMessage = { parts: [editPart], } -export async function setupTimelineBenchmark(page: Page, options: { historyTurns: number; eventBatch: number }) { +export async function setupTimelineBenchmark( + page: Page, + options: { + historyTurns: number + eventBatch: number + newLayoutDesigns?: boolean + vcsDiff?: unknown[] + turnDiffs?: unknown[] + }, +) { const events: EventPayload[] = [] let eventBatch = options.eventBatch + const currentUserMessage = options.turnDiffs + ? { ...userMessage, info: { ...userMessage.info, summary: { diffs: options.turnDiffs } } } + : userMessage await mockOpenCodeServer(page, { directory, project: project(), provider: provider(), sessions: [session()], + vcsDiff: options.vcsDiff, pageMessages: () => ({ items: [ ...Array.from({ length: options.historyTurns }, (_, index) => performanceTurn(index)).flat(), - userMessage, + currentUserMessage, assistantMessage, ], }), events: () => events.splice(0, eventBatch), eventRetry: 16, }) - await page.addInitScript(() => { - localStorage.setItem( - "settings.v3", - JSON.stringify({ - general: { - editToolPartsExpanded: true, - shellToolPartsExpanded: true, - showReasoningSummaries: true, - }, - }), - ) - }) + await page.addInitScript( + (input) => { + localStorage.setItem( + "settings.v3", + JSON.stringify({ + general: { + newLayoutDesigns: input.newLayoutDesigns, + editToolPartsExpanded: true, + shellToolPartsExpanded: true, + showReasoningSummaries: true, + }, + }), + ) + }, + { newLayoutDesigns: options.newLayoutDesigns ?? false }, + ) await page.setViewportSize({ width: 1366, height: 768 }) const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) const text = page.locator(`[data-timeline-part-id="${textPartID}"]`).first() @@ -278,7 +295,7 @@ function performanceTurn(index: number) { messageID: assistantID, type: "tool", callID: `call_0000_${suffix}_patch`, - tool: "apply_patch", + tool: "patch", state: { status: "completed", input: { patchText: realisticPatch(index) }, diff --git a/packages/app/e2e/performance/timeline/session-timeline-benchmark.spec.ts b/packages/app/e2e/performance/timeline/session-timeline-benchmark.spec.ts index 64d79283f06d..1a52bc8aa3ca 100644 --- a/packages/app/e2e/performance/timeline/session-timeline-benchmark.spec.ts +++ b/packages/app/e2e/performance/timeline/session-timeline-benchmark.spec.ts @@ -1,3 +1,4 @@ +import type { Page } from "@playwright/test" import { benchmark, benchmarkDiagnostics, expect } from "../benchmark" import { buildInitialStreamEvent, @@ -6,80 +7,300 @@ import { textPartID, } from "./session-timeline-benchmark.fixture" import { startTimelineProfile } from "./session-timeline-profile" +import { createReviewDiffs } from "./timeline-test-helpers" import { collectTimelineStreamMetrics, installTimelineStreamProbe, startTimelineStreamProbe, } from "./session-timeline-stream-probe" +type TimelineStreamOptions = { + newLayoutDesigns?: boolean + reviewDiffs?: boolean + reviewPane?: boolean +} + +type ReviewPaneSample = { + observedAtMs: number + panelVisible: boolean + header: string + diffViewers: number + diffLines: number + codeBlocks: number + ready: boolean +} + +type ReviewPaneProbe = { + samples: ReviewPaneSample[] + start: () => void + stop: () => void +} + +const reviewReadyStreak = 3 + benchmark.describe("performance: session timeline streaming", () => { benchmark("streams assistant text without remounting or oscillating", async ({ page, report }) => { - benchmark.setTimeout(480_000) - const cpuThrottle = Number(process.env.TIMELINE_CPU_THROTTLE ?? 30) - const deltaCount = Number(process.env.TIMELINE_DELTA_COUNT ?? 160) - const historyTurns = Number(process.env.TIMELINE_HISTORY_TURNS ?? 320) - const eventBatch = Number(process.env.TIMELINE_EVENT_BATCH ?? 1) - const minimal = process.env.TIMELINE_MINIMAL === "1" - const profileCPU = process.env.TIMELINE_CPU_PROFILE === "1" - const profileVisual = !minimal && profileCPU && process.env.TIMELINE_VISUAL_PROFILE !== "0" + benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000) + const result = await runTimelineStreamBenchmark(page, {}) + report(result.metrics, result.context) + }) + + benchmark("streams assistant text in v2 with review pane closed", async ({ page, report }) => { + benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000) + const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true }) + report(result.metrics, result.context) + }) + + benchmark("streams assistant text in v2 with review diffs and pane closed", async ({ page, report }) => { + benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000) + const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true, reviewDiffs: true }) + report(result.metrics, result.context) + }) + + benchmark("streams assistant text in v2 with review pane open", async ({ page, report }) => { + benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000) + const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true, reviewPane: true }) + report(result.metrics, result.context) + }) +}) + +benchmark.describe("performance: review pane", () => { + benchmark("loads v2 review diffs and switches active files", async ({ page, report }) => { + benchmark.setTimeout(240_000) + const historyTurns = Number(process.env.REVIEW_PANE_HISTORY_TURNS ?? 72) + const diffs = createReviewDiffs() const fixture = await setupTimelineBenchmark(page, { historyTurns, - eventBatch, + eventBatch: 1, + newLayoutDesigns: true, + vcsDiff: diffs, }) - fixture.transport.enqueue(buildInitialStreamEvent(deltaCount)) - const contentStart = performance.now() + fixture.transport.enqueue(buildInitialStreamEvent(1)) await expect(fixture.text).toBeVisible() await expect(fixture.text).toContainText("Implementation plan") - const initialContentObservedMs = performance.now() - contentStart await fixture.scrollToBottom() await fixture.waitForStableGeometry() - const profile = await startTimelineProfile(page, { cpuThrottle, profileCPU }) - await installTimelineStreamProbe(page, { textPartID, finalIndex: deltaCount, profileVisual, minimal }) - const deltas = buildStreamDeltaEvents(deltaCount) - await startTimelineStreamProbe(page) - fixture.transport.enqueue(deltas) - - await page.waitForFunction( - (finalIndex) => - ( - window as Window & { - __timelineStreamBenchmark?: { applied: { index: number }[] } - } - ).__timelineStreamBenchmark?.applied.some((value) => value.index === finalIndex), - deltaCount, - { timeout: 420_000 }, - ) - await expect(fixture.text).toContainText("benchmark-complete") - await expect(fixture.text).toContainText("Streaming") - await fixture.waitForStableGeometry() - const metrics = await collectTimelineStreamMetrics(page, { - textPartID, - finalIndex: deltaCount, - navigations: benchmarkDiagnostics(page).navigations, - }) - const delivered = deltas.length - fixture.transport.pendingCount() - await profile.stop() + const open = await measureReviewPaneLoad(page, diffs[0]!.file) + const switches = [] + for (const diff of diffs.slice(1, 4)) switches.push(await measureReviewNextFile(page, diff.file)) report( { - endToEndInitialContentObservedMs: initialContentObservedMs, - ...metrics, - deliveredDeltas: delivered, - pendingDeltas: fixture.transport.pendingCount(), + open, + switches, }, { - cpuThrottle, - profileCPU, - profileVisual, - minimal, - queuedDeltas: deltas.length, historyTurns, - eventBatch, + reviewDiffs: diffs.length, }, ) - - await profile.reset() }) }) + +async function runTimelineStreamBenchmark(page: Page, options: TimelineStreamOptions) { + const completionTimeoutMs = Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + const cpuThrottle = Number(process.env.TIMELINE_CPU_THROTTLE ?? 30) + const deltaCount = Number(process.env.TIMELINE_DELTA_COUNT ?? 160) + const historyTurns = Number(process.env.TIMELINE_HISTORY_TURNS ?? 320) + const eventBatch = Number(process.env.TIMELINE_EVENT_BATCH ?? 1) + const minimal = process.env.TIMELINE_MINIMAL === "1" + const profileCPU = process.env.TIMELINE_CPU_PROFILE === "1" + const profileVisual = !minimal && profileCPU && process.env.TIMELINE_VISUAL_PROFILE !== "0" + const diffs = options.reviewDiffs || options.reviewPane ? createReviewDiffs() : undefined + const fixture = await setupTimelineBenchmark(page, { + historyTurns, + eventBatch, + newLayoutDesigns: options.newLayoutDesigns, + // Turn diffs exercise timeline data cost; the pane-open scenario serves the same + // diffs through the default git mode so it works across review implementations. + turnDiffs: options.reviewDiffs ? diffs : undefined, + vcsDiff: options.reviewPane ? diffs : undefined, + }) + + fixture.transport.enqueue(buildInitialStreamEvent(deltaCount)) + const contentStart = performance.now() + await expect(fixture.text).toBeVisible() + await expect(fixture.text).toContainText("Implementation plan") + const initialContentObservedMs = performance.now() - contentStart + await fixture.scrollToBottom() + await fixture.waitForStableGeometry() + + const reviewPane = options.reviewPane && diffs ? await measureReviewPaneLoad(page, diffs[0]!.file) : undefined + if (reviewPane) await fixture.waitForStableGeometry() + + const profile = await startTimelineProfile(page, { cpuThrottle, profileCPU }) + await installTimelineStreamProbe(page, { textPartID, finalIndex: deltaCount, profileVisual, minimal }) + const deltas = buildStreamDeltaEvents(deltaCount) + await startTimelineStreamProbe(page) + fixture.transport.enqueue(deltas) + + await page.waitForFunction( + (finalIndex) => + ( + window as Window & { + __timelineStreamBenchmark?: { applied: { index: number }[] } + } + ).__timelineStreamBenchmark?.applied.some((value) => value.index === finalIndex), + deltaCount, + { timeout: completionTimeoutMs }, + ) + await expect(fixture.text).toContainText("benchmark-complete") + await expect(fixture.text).toContainText("Streaming") + await fixture.waitForStableGeometry() + const metrics = await collectTimelineStreamMetrics(page, { + textPartID, + finalIndex: deltaCount, + navigations: benchmarkDiagnostics(page).navigations, + }) + const delivered = deltas.length - fixture.transport.pendingCount() + await profile.stop() + + const result = { + metrics: { + endToEndInitialContentObservedMs: initialContentObservedMs, + ...metrics, + deliveredDeltas: delivered, + pendingDeltas: fixture.transport.pendingCount(), + reviewPane: reviewPane ?? null, + }, + context: { + cpuThrottle, + profileCPU, + profileVisual, + minimal, + queuedDeltas: deltas.length, + historyTurns, + eventBatch, + newLayoutDesigns: options.newLayoutDesigns === true, + reviewPane: options.reviewPane === true ? "open" : "closed", + reviewDiffs: diffs?.length ?? 0, + }, + } + + await profile.reset() + return result +} + +async function measureReviewPaneLoad(page: Page, file: string) { + // Default git mode reads the mocked /vcs/diff data, so opening the pane is enough + // and the flow works across review pane implementations. + await installReviewPaneProbe(page, { file }) + await startReviewPaneProbe(page) + await page.getByRole("button", { name: "Toggle review" }).click() + await expect(page.locator("#review-panel")).toBeVisible() + return collectReviewPaneProbe(page) +} + +async function measureReviewNextFile(page: Page, file: string) { + await installReviewPaneProbe(page, { file }) + await startReviewPaneProbe(page) + await page.getByRole("button", { name: "Next file" }).click() + return collectReviewPaneProbe(page) +} + +async function installReviewPaneProbe(page: Page, input: { file: string }) { + await page.evaluate((input) => { + const samples: ReviewPaneSample[] = [] + const basename = input.file.split(/[\\/]/).at(-1) ?? input.file + let started: number | undefined + let running = true + + const paneState = () => { + const panel = document.querySelector("#review-panel") + const review = panel?.querySelector('[data-component="session-review-v2"]') + const rect = (review ?? panel)?.getBoundingClientRect() + const text = panel?.textContent ?? "" + const previewHeader = panel?.querySelector( + '[data-slot="session-review-v2-file-header"]', + )?.textContent + const header = previewHeader ?? text + const viewers = panel ? [...panel.querySelectorAll('[data-component="file"][data-mode="diff"]')] : [] + const codeBlocks = panel?.querySelectorAll("code").length ?? 0 + const diffLines = viewers.reduce( + (sum, viewer) => + sum + + (viewer.shadowRoot?.querySelectorAll("[data-line]").length ?? viewer.querySelectorAll("[data-line]").length), + 0, + ) + const panelVisible = + !!panel && panel.getAttribute("aria-hidden") !== "true" && !!rect && rect.width > 0 && rect.height > 0 + return { + panelVisible, + header: header.slice(0, 500), + diffViewers: viewers.length, + diffLines, + codeBlocks, + ready: + panelVisible && + header.includes(basename) && + (viewers.length > 0 || text.includes("+3") || diffLines > 0 || codeBlocks > 0), + } + } + + const sample = () => { + if (!running || started === undefined) return + requestAnimationFrame(() => { + setTimeout(() => { + if (!running || started === undefined) return + samples.push({ observedAtMs: performance.now() - started, ...paneState() }) + if (performance.now() - started < 10_000) sample() + }, 0) + }) + } + + ;(window as Window & { __reviewPaneProbe?: ReviewPaneProbe }).__reviewPaneProbe = { + samples, + start: () => { + started = performance.now() + performance.mark("opencode.review-pane.click") + sample() + }, + stop: () => { + running = false + }, + } + }, input) +} + +async function startReviewPaneProbe(page: Page) { + await page.evaluate(() => { + ;(window as Window & { __reviewPaneProbe?: ReviewPaneProbe }).__reviewPaneProbe!.start() + }) +} + +async function collectReviewPaneProbe(page: Page) { + await page.waitForFunction((streak) => { + const samples = (window as Window & { __reviewPaneProbe?: ReviewPaneProbe }).__reviewPaneProbe?.samples + if (!samples) return false + return samples.some((_, index) => { + const stable = samples.slice(index, index + streak) + return stable.length === streak && stable.every((sample) => sample.ready) + }) + }, reviewReadyStreak) + + const samples = await page.evaluate(() => { + const probe = (window as Window & { __reviewPaneProbe?: ReviewPaneProbe }).__reviewPaneProbe! + probe.stop() + return probe.samples + }) + return { summary: summarizeReviewPaneSamples(samples), samples } +} + +function summarizeReviewPaneSamples(samples: ReviewPaneSample[]) { + const firstReady = samples.find((sample) => sample.ready) + const stableIndex = samples.findIndex((_, index) => { + const stable = samples.slice(index, index + reviewReadyStreak) + return stable.length === reviewReadyStreak && stable.every((sample) => sample.ready) + }) + return { + samples: samples.length, + firstReadyObservedMs: firstReady?.observedAtMs ?? null, + stableReadyObservedMs: stableIndex === -1 ? null : samples[stableIndex + reviewReadyStreak - 1]!.observedAtMs, + notReadySamples: samples.filter((sample) => !sample.ready).length, + maxDiffViewers: Math.max(0, ...samples.map((sample) => sample.diffViewers)), + maxDiffLines: Math.max(0, ...samples.map((sample) => sample.diffLines)), + maxCodeBlocks: Math.max(0, ...samples.map((sample) => sample.codeBlocks)), + } +} diff --git a/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts b/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts index 529081a1d9e0..2e20d98415cf 100644 --- a/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts +++ b/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts @@ -131,7 +131,7 @@ function toolPart( ): MessagePart { const metadata = metadataOverride ?? - (tool === "apply_patch" + (tool === "patch" ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } : tool === "edit" || tool === "write" ? { @@ -219,7 +219,7 @@ function turn(index: number): Message[] { ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] : []), ...(index % 8 === 0 - ? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] + ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] : []), ...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)] @@ -269,7 +269,6 @@ const childMessages = Array.from({ length: 4 }, (_, index) => [ ]).flat() function renderable(part: MessagePart) { - if (part.type === "tool" && part.tool === "todowrite") return false if (part.type === "text") return !!part.text.trim() if (part.type === "reasoning") return !!part.text.trim() return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch" diff --git a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts index 401fb74496cd..5028373c55f6 100644 --- a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts +++ b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts @@ -21,7 +21,10 @@ export async function installTimelineSettings(page: Page) { export function mockStressTimeline( page: Page, - input?: { onMessages?: (input: { sessionID: string; before?: string; phase: "start" | "end" }) => void }, + input?: { + onMessages?: (input: { sessionID: string; before?: string; phase: "start" | "end" }) => void + vcsDiff?: unknown[] + }, ) { return mockOpenCodeServer(page, { sessions: fixture.sessions, @@ -30,6 +33,7 @@ export function mockStressTimeline( project: fixture.project, pageMessages, onMessages: input?.onMessages, + vcsDiff: input?.vcsDiff, }) } @@ -45,7 +49,7 @@ export async function installStressSessionTabs(page: Page, input?: { draftID?: s }), ) localStorage.setItem( - "opencode.global.dat:tabs", + "opencode.window.browser.dat:tabs", JSON.stringify([ ...sessionIDs.map((sessionId) => ({ type: "session", @@ -78,3 +82,53 @@ export function stressDraftHref(draftID: string) { function stressServer() { return `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` } + +export function createReviewDiffs() { + return Array.from({ length: Number(process.env.REVIEW_PANE_DIFF_COUNT ?? 72) }, (_, index) => { + const lines = index % 3 === 0 ? 300 : index % 3 === 1 ? 120 : 38 + const file = `src/review/generated-${String(index).padStart(3, "0")}.ts` + const before = reviewSource(index, lines) + const after = before + .replace(`value_${index}_4`, `updated_${index}_4`) + .replace( + `value_${index}_${Math.max(8, Math.floor(lines / 2))}`, + `updated_${index}_${Math.max(8, Math.floor(lines / 2))}`, + ) + .replace(`value_${index}_${lines - 4}`, `updated_${index}_${lines - 4}`) + return { + file, + patch: reviewPatch(file, before, after), + additions: 3, + deletions: 3, + status: "modified" as const, + } + }) +} + +function reviewSource(seed: number, lines: number) { + return Array.from( + { length: lines }, + (_, index) => `export const value_${seed}_${index} = "${reviewWords(seed + index, index % 5 === 0 ? 180 : 42)}"`, + ).join("\n") +} + +function reviewPatch(file: string, before: string, after: string) { + const beforeLines = before.split("\n") + const afterLines = after.split("\n") + return [ + `diff --git a/${file} b/${file}`, + `--- a/${file}`, + `+++ b/${file}`, + `@@ -1,${beforeLines.length} +1,${afterLines.length} @@`, + ...beforeLines.flatMap((line, index) => { + const next = afterLines[index]! + if (line === next) return [` ${line}`] + return [`-${line}`, `+${next}`] + }), + ].join("\n") +} + +function reviewWords(seed: number, length: number) { + const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet"] + return Array.from({ length: Math.ceil(length / 7) }, (_, index) => words[(seed + index * 3) % words.length]).join(" ") +} diff --git a/packages/app/e2e/performance/unit/mock-server.test.ts b/packages/app/e2e/performance/unit/mock-server.test.ts new file mode 100644 index 000000000000..83308c0a866b --- /dev/null +++ b/packages/app/e2e/performance/unit/mock-server.test.ts @@ -0,0 +1,46 @@ +import { expect, test } from "bun:test" +import type { Page, Route } from "@playwright/test" +import { mockOpenCodeServer } from "../../utils/mock-server" + +test("applies message latency after a list response gate is released", async () => { + const events: string[] = [] + const gate = Promise.withResolvers() + let handler: ((route: Route) => Promise) | undefined + const page = { + route: (_url: string, callback: (route: Route) => Promise) => { + handler = callback + return Promise.resolve() + }, + } as unknown as Page + await mockOpenCodeServer(page, { + provider: {}, + directory: "C:/OpenCode", + project: {}, + sessions: [{ id: "session" }], + messageDelay: 25, + beforeMessagesResponse: () => { + events.push("before") + return gate.promise + }, + onMessages: (request) => events.push(request.phase), + pageMessages: () => { + events.push("page") + return { items: [] } + }, + }) + + const response = handler!({ + request: () => ({ url: () => "http://127.0.0.1:4096/session/session/message" }), + fulfill: () => { + events.push("fulfill") + return Promise.resolve() + }, + } as unknown as Route) + expect(events).toEqual(["start", "before"]) + + const released = performance.now() + gate.resolve() + await response + expect(performance.now() - released).toBeGreaterThanOrEqual(20) + expect(events).toEqual(["start", "before", "page", "end", "fulfill"]) +}) diff --git a/packages/app/e2e/performance/unit/session-tab-switch-metrics.test.ts b/packages/app/e2e/performance/unit/session-tab-switch-metrics.test.ts index dd771b7d57c9..4b824d9dfb0d 100644 --- a/packages/app/e2e/performance/unit/session-tab-switch-metrics.test.ts +++ b/packages/app/e2e/performance/unit/session-tab-switch-metrics.test.ts @@ -52,3 +52,35 @@ test("reports missing correctness without throwing", () => { expect(result.firstCorrectObservedMs).toBeNull() expect(result.stableObservedMs).toBeNull() }) + +test("requires an explicitly tracked part to be visible", () => { + const result = classifySessionSwitch([ + { + observedAtMs: 16, + destination: ["destination"], + source: [], + hasVisibleRows: true, + last: true, + requiredPartVisible: false, + bottomErrorPx: 0, + }, + ]) + + expect(result.firstCorrectObservedMs).toBeNull() +}) + +test("can measure content correctness without requiring a bottom anchor", () => { + const result = classifySessionSwitch([ + { + observedAtMs: 16, + destination: ["destination"], + source: [], + hasVisibleRows: true, + last: true, + requiredPartVisible: true, + bottomAnchorRequired: false, + }, + ]) + + expect(result.firstCorrectObservedMs).toBe(16) +}) diff --git a/packages/app/e2e/performance/unit/session-tab-switch-probe.test.ts b/packages/app/e2e/performance/unit/session-tab-switch-probe.test.ts new file mode 100644 index 000000000000..d5d7f09a69ef --- /dev/null +++ b/packages/app/e2e/performance/unit/session-tab-switch-probe.test.ts @@ -0,0 +1,55 @@ +import { expect, test } from "bun:test" +import type { Page } from "@playwright/test" +import { measureSessionSwitch } from "../timeline/session-tab-switch-probe" + +function testPage(waitFailure?: Error) { + const stops: unknown[] = [] + const page = { + evaluate: async (_callback: unknown, input?: unknown) => { + if (input) return + stops.push(undefined) + }, + waitForFunction: async () => { + if (waitFailure) throw waitFailure + }, + } as unknown as Page + return { page, stops } +} + +function input(run: () => Promise) { + return { + destinationIDs: ["destination"], + sourceIDs: ["source"], + lastID: "destination", + href: "/session/destination", + switch: run, + } +} + +test("stops sampling when the session switch fails", async () => { + const failure = new Error("switch failed") + const context = testPage() + + await expect( + measureSessionSwitch( + context.page, + input(async () => Promise.reject(failure)), + ), + ).rejects.toBe(failure) + + expect(context.stops).toHaveLength(1) +}) + +test("stops sampling when the stable wait fails", async () => { + const failure = new Error("stable wait failed") + const context = testPage(failure) + + await expect( + measureSessionSwitch( + context.page, + input(async () => {}), + ), + ).rejects.toBe(failure) + + expect(context.stops).toHaveLength(1) +}) diff --git a/packages/app/e2e/performance/unit/visual-stability.test.ts b/packages/app/e2e/performance/unit/visual-stability.test.ts new file mode 100644 index 000000000000..2fc3be316660 --- /dev/null +++ b/packages/app/e2e/performance/unit/visual-stability.test.ts @@ -0,0 +1,392 @@ +import { expect, test } from "bun:test" +import { + analyzeVisualStability, + analyzeVisualStabilityByMarker, + type VisualStabilityTrace, +} from "../../utils/visual-stability" +import { analyzeVisualObservations } from "../../utils/visual-stability/analyzer" +import { legacyVisualPlan, visualPlan, type VisualInvariant } from "../../utils/visual-stability/invariant" +import { defineVisualRegions, mapVisualRegions } from "../../utils/visual-stability/regions" + +function trace(samples: VisualStabilityTrace["samples"]): VisualStabilityTrace { + return { markers: [], samples } +} + +test("accepts continuous visible motion", () => { + expect( + analyzeVisualStability( + trace([ + frame(0, region({ width: 80, bottom: 40 }), region({ top: 40, bottom: 60 })), + frame(16, region({ width: 75, bottom: 45 }), region({ top: 45, bottom: 65 })), + frame(32, region({ width: 70, bottom: 50 }), region({ top: 50, bottom: 70 })), + ]), + { flow: ["changing", "following"] }, + ), + ).toEqual([]) +}) + +test("reports repeated geometry reversals", () => { + const issues = analyzeVisualStability( + trace([ + frame(0, region({ width: 80 })), + frame(16, region({ width: 60 })), + frame(32, region({ width: 78 })), + frame(48, region({ width: 62 })), + ]), + ) + + expect(issues.some((issue) => issue.includes("changing width reversed 2 times"))).toBe(true) +}) + +test("reports visible blanking, label reversal, and overlap", () => { + const issues = analyzeVisualStability( + trace([ + frame(0, region({ label: "Exploring", opacity: 1, bottom: 40 }), region({ top: 40, bottom: 60 })), + frame(16, region({ label: "Explored", opacity: 0.2, bottom: 50 }), region({ top: 49, bottom: 69 })), + frame(32, region({ label: "Exploring", opacity: 1, bottom: 50 }), region({ top: 50, bottom: 70 })), + ]), + { flow: ["changing", "following"] }, + ) + + expect(issues.some((issue) => issue.includes("opacity fell to 0.2"))).toBe(true) + expect(issues.some((issue) => issue.includes("label reverted"))).toBe(true) + expect(issues.some((issue) => issue.includes("overlapped following by 1px"))).toBe(true) +}) + +test("reports duplicate regions and unexpected remounts", () => { + const issues = analyzeVisualStability( + trace([frame(0, region({ node: 1 })), frame(16, region({ node: 2, count: 2 })), frame(32, region({ node: 2 }))]), + { stable: ["changing"], unique: ["changing"] }, + ) + + expect(issues.some((issue) => issue.includes("changing appeared 2 times"))).toBe(true) + expect(issues.some((issue) => issue.includes("changing remounted"))).toBe(true) +}) + +test("reports bottom anchor loss but permits movement while scrolled away", () => { + const anchored = analyzeVisualStability( + trace([ + { at: 0, regions: { changing: region() }, viewport: viewport(0) }, + { at: 16, regions: { changing: region() }, viewport: viewport(24) }, + ]), + { preserveBottomAnchor: true }, + ) + const away = analyzeVisualStability( + trace([ + { at: 0, regions: { changing: region() }, viewport: viewport(80) }, + { at: 16, regions: { changing: region() }, viewport: viewport(104) }, + ]), + { preserveBottomAnchor: true }, + ) + + expect(anchored.some((issue) => issue.includes("bottom anchor moved to 24px"))).toBe(true) + expect(away).toEqual([]) +}) + +test("reports up down up movement while preserving a bottom anchor", () => { + const issues = analyzeVisualStability( + trace([ + { at: 0, regions: { changing: region({ top: 200, bottom: 240 }) }, viewport: viewport(0) }, + { at: 16, regions: { changing: region({ top: 180, bottom: 220 }) }, viewport: viewport(0) }, + { at: 32, regions: { changing: region({ top: 196, bottom: 236 }) }, viewport: viewport(0) }, + { at: 48, regions: { changing: region({ top: 176, bottom: 216 }) }, viewport: viewport(0) }, + ]), + { preserveBottomAnchor: true, maxPositionReversals: 0 }, + ) + + expect(issues.some((issue) => issue.includes("changing top reversed 2 times"))).toBe(true) + expect(issues.some((issue) => issue.includes("changing bottom reversed 2 times"))).toBe(true) +}) + +test("accepts monotonic upward movement while preserving a bottom anchor", () => { + expect( + analyzeVisualStability( + trace([ + { at: 0, regions: { changing: region({ top: 200, bottom: 240 }) }, viewport: viewport(0) }, + { at: 16, regions: { changing: region({ top: 190, bottom: 230 }) }, viewport: viewport(0) }, + { at: 32, regions: { changing: region({ top: 180, bottom: 220 }) }, viewport: viewport(0) }, + ]), + { preserveBottomAnchor: true, maxPositionReversals: 0 }, + ), + ).toEqual([]) +}) + +test("ignores overlap entirely outside the clipped timeline viewport", () => { + expect( + analyzeVisualStability( + trace([ + { + at: 0, + regions: { + changing: region({ top: -200, bottom: -100 }), + following: region({ top: -150, bottom: -50 }), + }, + viewport: viewport(0), + }, + ]), + { flow: ["changing", "following"] }, + ), + ).toEqual([]) +}) + +test("reports visible anchor movement while allowing virtual scrollbar movement", () => { + const issues = analyzeVisualStability( + trace([ + { at: 0, regions: { anchor: region({ top: 100, bottom: 120 }) }, viewport: { ...viewport(100), scrollTop: 40 } }, + { at: 16, regions: { anchor: region({ top: 100, bottom: 120 }) }, viewport: { ...viewport(120), scrollTop: 60 } }, + ]), + { fixed: ["anchor"] }, + ) + + expect(issues).toEqual([]) + + const moved = analyzeVisualStability( + trace([ + { at: 0, regions: { anchor: region({ top: 100, bottom: 120 }) }, viewport: viewport(100) }, + { at: 16, regions: { anchor: region({ top: 112, bottom: 132 }) }, viewport: viewport(100) }, + ]), + { fixed: ["anchor"] }, + ) + expect(moved.some((issue) => issue.includes("anchor moved 12px in the viewport"))).toBe(true) +}) + +test("analyzes each marked event independently", () => { + const input: VisualStabilityTrace = { + markers: [ + { at: 10, label: "grow" }, + { at: 40, label: "shrink" }, + ], + samples: [ + frame(0, region({ top: 100 })), + frame(16, region({ top: 90 })), + frame(32, region({ top: 80 })), + frame(48, region({ top: 90 })), + frame(64, region({ top: 100 })), + ], + } + + expect(analyzeVisualStability(input, { maxPositionReversals: 0 })).toContain("changing top reversed 1 times") + expect( + analyzeVisualStabilityByMarker(input, { + maxPositionReversals: 0, + motion: ["changing"], + aggregateMotion: false, + }), + ).toEqual([]) +}) + +test("reports cross-event motion reversals by default", () => { + const input: VisualStabilityTrace = { + markers: [ + { at: 10, label: "up" }, + { at: 40, label: "down" }, + ], + samples: [ + frame(0, region({ top: 100 })), + frame(16, region({ top: 90 })), + frame(32, region({ top: 80 })), + frame(48, region({ top: 90 })), + frame(64, region({ top: 100 })), + ], + } + + expect(analyzeVisualStabilityByMarker(input, { maxPositionReversals: 0 })).toContain("changing top reversed 1 times") +}) + +test("reports regions rendered in the wrong flow order", () => { + const issues = analyzeVisualStability( + trace([frame(0, region({ top: 100, bottom: 120 }), region({ top: 60, bottom: 80 }))]), + { flow: ["changing", "following"] }, + ) + + expect(issues.some((issue) => issue.includes("changing rendered after following"))).toBe(true) +}) + +test("uses painted bounds instead of clipped layout overflow", () => { + expect( + analyzeVisualStability( + trace([ + frame( + 0, + region({ top: 100, bottom: 140, height: 40, layoutTop: 100, layoutBottom: 300 }), + region({ top: 140, bottom: 180 }), + ), + ]), + { flow: ["changing", "following"] }, + ), + ).toEqual([]) +}) + +test("does not report disappearance when a present row moves outside the viewport", () => { + expect( + analyzeVisualStability( + trace([ + frame(0, region({ visible: true })), + frame(16, region({ visible: false, inViewport: false, top: -100, bottom: -80 })), + frame(32, region({ visible: true })), + ]), + ), + ).toEqual([]) +}) + +test("reports an in-viewport transparent frame between visible frames", () => { + const issues = analyzeVisualStability( + trace([ + frame(0, region()), + frame(16, region({ visible: false, opacity: 0, inViewport: true })), + frame(32, region()), + ]), + ) + + expect(issues.some((issue) => issue.includes("blanked between visible frames"))).toBe(true) +}) + +test("reports an in-viewport display-none frame between visible frames", () => { + const issues = analyzeVisualStability( + trace([ + frame(0, region()), + frame(16, region({ visible: false, width: 0, height: 0, inViewport: true, cssHidden: true })), + frame(32, region()), + ]), + ) + + expect(issues.some((issue) => issue.includes("blanked between visible frames"))).toBe(true) +}) + +test("can limit motion analysis to unaffected regions", () => { + expect( + analyzeVisualStability( + trace([ + frame(0, region({ height: 20 }), region()), + frame(16, region({ height: 40 }), region()), + frame(32, region({ height: 30 }), region()), + ]), + { motion: ["following"] }, + ), + ).toEqual([]) +}) + +test("reports a blank frame across replacement surfaces", () => { + const issues = analyzeVisualStability( + { + markers: [], + samples: [ + { at: 0, regions: { thinking: region(), error: region({ present: false, visible: false }) } }, + { + at: 16, + regions: { + thinking: region({ present: false, visible: false }), + error: region({ present: false, visible: false }), + }, + }, + { at: 32, regions: { thinking: region({ present: false, visible: false }), error: region() } }, + ], + }, + { continuousAny: [["thinking", "error"]] }, + ) + + expect(issues.some((issue) => issue.includes("thinking | error blanked"))).toBe(true) +}) + +test("reports failure to acquire the bottom anchor", () => { + const issues = analyzeVisualStability( + trace([ + { at: 0, regions: { changing: region() }, viewport: viewport(600) }, + { at: 16, regions: { changing: region() }, viewport: viewport(120) }, + ]), + { acquireBottomAnchor: true }, + ) + + expect(issues.some((issue) => issue.includes("did not acquire bottom anchor"))).toBe(true) +}) + +test("reports a required region that never renders", () => { + const issues = analyzeVisualStability( + trace([ + { + at: 0, + regions: { changing: region({ present: false, visible: false }) }, + }, + ]), + { required: ["changing"] }, + ) + + expect(issues).toContain("changing never rendered") +}) + +test("preserves typed region names while mapping definitions", () => { + const regions = defineVisualRegions({ + changing: { selector: "[data-changing]" }, + following: { selector: "[data-following]", closest: "[data-row]" }, + }) + const selectors = mapVisualRegions(regions, (region) => region.selector) + + expect(selectors).toEqual({ changing: "[data-changing]", following: "[data-following]" }) + const name: keyof typeof selectors = "changing" + expect(name).toBe("changing") +}) + +test("evaluates the typed invariant algebra over explicit observations", () => { + const regions = defineVisualRegions({ + changing: { selector: "[data-changing]" }, + following: { selector: "[data-following]" }, + }) + const invariants = [ + { type: "required", regions: ["changing"] }, + { type: "flow", regions: ["changing", "following"] }, + ] satisfies VisualInvariant[] + // @ts-expect-error Plans reject names that are not in the region definition. + const invalid = { type: "required", regions: ["missing"] } satisfies VisualInvariant + const plan = visualPlan(regions, invariants, { perMarker: true }) + + expect(invalid.regions).toEqual(["missing"]) + expect(plan.perMarker).toBe(true) + expect(analyzeVisualObservations([frame(0, region({ bottom: 50 }), region({ top: 49, bottom: 69 }))], plan)).toEqual([ + "changing overlapped following by 1px at 0ms", + ]) +}) + +test("legacy plan adapter preserves analyzer messages and order", () => { + const input = trace([ + frame(0, region({ label: "Exploring", opacity: 1, bottom: 40 }), region({ top: 40, bottom: 60 })), + frame(16, region({ label: "Explored", opacity: 0.2, bottom: 50 }), region({ top: 49, bottom: 69 })), + frame(32, region({ label: "Exploring", opacity: 1, bottom: 50 }), region({ top: 50, bottom: 70 })), + ]) + const options = { flow: ["changing", "following"], stable: ["changing"] } + + expect(analyzeVisualObservations(input.samples, legacyVisualPlan(options))).toEqual( + analyzeVisualStability(input, options), + ) +}) + +function frame( + at: number, + changing: VisualStabilityTrace["samples"][number]["regions"][string], + following?: VisualStabilityTrace["samples"][number]["regions"][string], +) { + return { at, regions: { changing, ...(following ? { following } : {}) } } +} + +function region(input: Partial = {}) { + return { + present: true, + visible: true, + inViewport: true, + top: 0, + bottom: 20, + width: 100, + height: 20, + opacity: 1, + count: 1, + node: 1, + label: "", + text: "", + layoutTop: input.top ?? 0, + layoutBottom: input.bottom ?? 20, + ...input, + } +} + +function viewport(distanceFromBottom: number) { + return { top: 0, bottom: 400, scrollTop: 100, scrollHeight: 500, clientHeight: 400, distanceFromBottom } +} diff --git a/packages/app/e2e/regression/cross-server-tab-close.spec.ts b/packages/app/e2e/regression/cross-server-tab-close.spec.ts index 031440f16602..9fd799143729 100644 --- a/packages/app/e2e/regression/cross-server-tab-close.spec.ts +++ b/packages/app/e2e/regression/cross-server-tab-close.spec.ts @@ -14,7 +14,7 @@ test("closing the active server's last tab opens the remaining server tab", asyn localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] })) localStorage.setItem( - "opencode.global.dat:tabs", + "opencode.window.browser.dat:tabs", JSON.stringify([ { type: "session", server: "http://127.0.0.1:4096", sessionId: sessionA }, { type: "session", server: serverB, sessionId: sessionB }, @@ -52,7 +52,7 @@ test("legacy session routes preserve an existing tab's server", async ({ page }) localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] })) localStorage.setItem( - "opencode.global.dat:tabs", + "opencode.window.browser.dat:tabs", JSON.stringify([{ type: "session", server: serverB, sessionId: sessionB }]), ) }, @@ -90,7 +90,7 @@ async function mockServers(page: Page, requests: string[]) { if (url.pathname === `/session/${current.id}`) return json(route, current) if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) if (url.pathname === `/session/${current.id}/message`) return json(route, []) - if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, []) + if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, []) if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) return json(route, []) if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname)) diff --git a/packages/app/e2e/regression/legacy-new-session.spec.ts b/packages/app/e2e/regression/legacy-new-session.spec.ts new file mode 100644 index 000000000000..30233a5aae10 --- /dev/null +++ b/packages/app/e2e/regression/legacy-new-session.spec.ts @@ -0,0 +1,40 @@ +import { expect, test } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { mockOpenCodeServer } from "../utils/mock-server" + +const draftID = "draft_legacy_new_session" +const directory = "C:/OpenCode/LegacyNewSession" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test("redirects a draft to the legacy new-session route", async ({ page }) => { + await mockOpenCodeServer(page, { + directory, + project: { + id: "proj_legacy_new_session", + worktree: directory, + vcs: "git", + name: "legacy-new-session", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { all: [], connected: [], default: {} }, + sessions: [], + pageMessages: () => ({ items: [] }), + }) + await page.addInitScript( + ({ directory, draftID, server }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } })) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([{ type: "draft", draftID, server, directory }]), + ) + }, + { directory, draftID, server }, + ) + + await page.goto(`/new-session?draftId=${draftID}`) + + await expect(page).toHaveURL(`/${base64Encode(directory)}/session`) + await expect(page.locator("header[data-tauri-drag-region]")).toBeVisible() + await expect(page.locator('[data-component="prompt-input"]')).toBeVisible() +}) diff --git a/packages/app/e2e/regression/new-session-panel-corner.spec.ts b/packages/app/e2e/regression/new-session-panel-corner.spec.ts new file mode 100644 index 000000000000..a17b8c081709 --- /dev/null +++ b/packages/app/e2e/regression/new-session-panel-corner.spec.ts @@ -0,0 +1,83 @@ +import { expect, test } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectAppVisible } from "../utils/waits" + +const draftID = "draft_new_session_panel_corner" +const directory = "C:/OpenCode/NewSessionPanelCorner" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test.use({ + viewport: { width: 935, height: 522 }, + deviceScaleFactor: 1, +}) + +test("matches the rounded panel corners to the dark new-session background", async ({ page }, testInfo) => { + await mockOpenCodeServer(page, { + directory, + project: { + id: "proj_new_session_panel_corner", + worktree: directory, + vcs: "git", + name: "new-session-panel-corner", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { all: [], connected: [], default: {} }, + sessions: [], + pageMessages: () => ({ items: [] }), + }) + await page.addInitScript( + ({ directory, draftID, server }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem("opencode-theme-id", "oc-2") + localStorage.setItem("opencode-color-scheme", "dark") + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([{ type: "draft", draftID, server, directory }]), + ) + }, + { directory, draftID, server }, + ) + + await page.goto(`/new-session?draftId=${draftID}`) + await expectAppVisible(page.locator('[data-component="prompt-input"]')) + await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark") + const panel = page.locator('main div[class*="rounded-[10px]"][class*="overflow-hidden"]') + await expect(panel).toHaveCount(1) + const box = await panel.boundingBox() + if (!box) throw new Error("New-session panel bounds are unavailable") + + const screenshot = await page.screenshot({ path: testInfo.outputPath("new-session-dark.png") }) + const corners = await page.evaluate( + async ({ source, points }) => { + const image = new Image() + image.src = source + await image.decode() + const canvas = document.createElement("canvas") + canvas.width = image.naturalWidth + canvas.height = image.naturalHeight + const context = canvas.getContext("2d") + if (!context) throw new Error("2D canvas is unavailable") + context.drawImage(image, 0, 0) + return points.map((point) => Array.from(context.getImageData(point.x, point.y, 1, 1).data)) + }, + { + source: `data:image/png;base64,${screenshot.toString("base64")}`, + points: [ + { x: Math.floor(box.x), y: Math.floor(box.y) }, + { x: Math.ceil(box.x + box.width) - 1, y: Math.floor(box.y) }, + { x: Math.floor(box.x), y: Math.ceil(box.y + box.height) - 1 }, + { x: Math.ceil(box.x + box.width) - 1, y: Math.ceil(box.y + box.height) - 1 }, + ], + }, + ) + + expect(corners.every(([red, green, blue, alpha]) => red <= 8 && green <= 8 && blue <= 8 && alpha === 255)).toBe(true) +}) diff --git a/packages/app/e2e/regression/prompt-thinking-level.spec.ts b/packages/app/e2e/regression/prompt-thinking-level.spec.ts index d12684c143fd..4219699f28c8 100644 --- a/packages/app/e2e/regression/prompt-thinking-level.spec.ts +++ b/packages/app/e2e/regression/prompt-thinking-level.spec.ts @@ -66,7 +66,7 @@ test("shows the V2 thinking level control while relevant", async ({ page }) => { await expect(control).toBeVisible() await control.locator('[data-action="prompt-model-variant"]').click() - const high = page.getByRole("option", { name: "high" }) + const high = page.getByRole("menuitemradio", { name: "high" }) await expect(high).toBeVisible() await page.mouse.move(0, 0) await expect(control).toBeVisible() diff --git a/packages/app/e2e/regression/remote-tab-busy.spec.ts b/packages/app/e2e/regression/remote-tab-busy.spec.ts new file mode 100644 index 000000000000..ad7e2e1d9906 --- /dev/null +++ b/packages/app/e2e/regression/remote-tab-busy.spec.ts @@ -0,0 +1,109 @@ +import { expect, test, type Page, type Route } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" + +const serverA = "http://127.0.0.1:4096" +const serverB = "http://127.0.0.1:4097" +const sessionA = session("ses_server_a", "C:/server-a", "Server A session") +const sessionB = session("ses_server_b", "/home/server-b", "Server B session") + +test("tab busy indicator reflects the tab server's own session status", async ({ page }) => { + await mockServers(page) + await page.addInitScript( + ({ serverA, serverB, sessionA, sessionB }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] })) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([ + { type: "session", server: serverA, sessionId: sessionA }, + { type: "session", server: serverB, sessionId: sessionB }, + ]), + ) + }, + { serverA, serverB, sessionA: sessionA.id, sessionB: sessionB.id }, + ) + + const hrefA = `/server/${base64Encode(serverA)}/session/${sessionA.id}` + const hrefB = `/server/${base64Encode(serverB)}/session/${sessionB.id}` + await page.goto(hrefA) + await expect(page.getByText(sessionA.title).first()).toBeVisible() + + // Session B is busy on server B while server A stays the active server, so the + // busy indicator must come from the tab server's status, not the active server's. + const tabB = page.locator(`[data-titlebar-tab-slot]:has(a[href="${hrefB}"])`) + await expect(tabB.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible() + + const tabA = page.locator(`[data-titlebar-tab-slot]:has(a[href="${hrefA}"])`) + await expect(tabA.locator("[data-titlebar-tab-title]")).toHaveText(sessionA.title) + await expect(tabA.locator('[data-component="session-progress-indicator-v2"]')).toHaveCount(0) +}) + +function session(id: string, directory: string, title: string) { + return { + id, + slug: id, + projectID: `project-${id}`, + directory, + title, + version: "dev", + time: { created: 1, updated: 1 }, + } +} + +async function mockServers(page: Page) { + await page.route("**/*", async (route) => { + const url = new URL(route.request().url()) + if (url.origin !== serverA && url.origin !== serverB) return route.fallback() + const current = url.origin === serverA ? sessionA : sessionB + const directory = url.searchParams.get("directory") + if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500) + if (url.pathname === "/global/event" || url.pathname === "/event") return sse(route) + if (url.pathname === "/global/health") return json(route, { healthy: true }) + if (url.pathname === "/session/status") + return json(route, url.origin === serverB ? { [sessionB.id]: { type: "busy" } } : {}) + if (url.pathname === "/session") return json(route, [current]) + if (url.pathname === `/session/${current.id}`) return json(route, current) + if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) + if (url.pathname === `/session/${current.id}/message`) return json(route, []) + if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, []) + if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) + return json(route, []) + if (["/global/config", "/config", "/provider/auth", "/mcp"].includes(url.pathname)) return json(route, {}) + if (url.pathname === "/provider") + return json(route, { all: [], connected: [], default: { providerID: "", modelID: "" } }) + if (url.pathname === "/agent") return json(route, [{ name: "build", mode: "primary" }]) + if (url.pathname === "/project" || url.pathname === "/project/current") { + const project = { + id: current.projectID, + worktree: current.directory, + vcs: "git", + time: { created: 1, updated: 1 }, + sandboxes: [], + } + return json(route, url.pathname === "/project" ? [project] : project) + } + if (url.pathname === "/path") + return json(route, { + state: current.directory, + config: current.directory, + worktree: current.directory, + directory: current.directory, + home: current.directory, + }) + if (url.pathname === "/vcs") return json(route, { branch: "main", default_branch: "main" }) + return json(route, {}) + }) +} + +function json(route: Route, body: unknown, status = 200) { + return route.fulfill({ + status, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify(body), + }) +} + +function sse(route: Route) { + return route.fulfill({ status: 200, contentType: "text/event-stream", body: ": ok\n\n" }) +} diff --git a/packages/app/e2e/regression/review-image-flash.spec.ts b/packages/app/e2e/regression/review-image-flash.spec.ts new file mode 100644 index 000000000000..dd200384d49a --- /dev/null +++ b/packages/app/e2e/regression/review-image-flash.spec.ts @@ -0,0 +1,202 @@ +import { expect, test, type Page } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectAppVisible, expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewImageFlashRegression" +const sessionID = "ses_review_image_flash_regression" +const title = "Review image flash regression" +const imageFile = "assets/preview.png" + +test("clicking an image file in the v2 review pane does not blank the panel", async ({ page }) => { + await openReview(page) + await installReviewFlashProbe(page) + + await page.getByRole("button", { name: /preview\.png/ }).click() + await waitForReviewFlashProbe(page, 400) + const trace = await collectReviewFlashProbe(page) + const bad = trace.samples.filter((sample) => sample.blank || sample.blackCenter) + + expect(trace.samples.length).toBeGreaterThan(0) + expect( + bad, + JSON.stringify({ bad: bad.slice(0, 8), first: trace.samples.slice(0, 8), last: trace.samples.slice(-4) }, null, 2), + ).toEqual([]) +}) + +async function openReview(page: Page) { + await page.setViewportSize({ width: 960, height: 900 }) + await page.addInitScript(() => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + }) + await mockOpenCodeServer(page, { + directory, + project: { + id: "proj_review_image_flash_regression", + worktree: directory, + vcs: "git", + name: "review-image-flash-regression", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { all: [], connected: [], default: {} }, + sessions: [ + { + id: sessionID, + slug: "review-image-flash-regression", + projectID: "proj_review_image_flash_regression", + directory, + title, + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + }, + ], + vcsDiff: [ + { + file: "src/example.ts", + additions: 1, + deletions: 1, + status: "modified", + patch: + "diff --git a/src/example.ts b/src/example.ts\n--- a/src/example.ts\n+++ b/src/example.ts\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n", + }, + { + file: imageFile, + patch: "", + additions: 1, + deletions: 0, + status: "added", + }, + ], + fileContent: async (path) => { + if (path !== imageFile) return undefined + await new Promise((resolve) => setTimeout(resolve, 250)) + return { + type: "binary", + content: "iVBORw0KGgo=", + encoding: "base64", + mimeType: "image/png", + } + }, + fileList: (path) => { + if (!path) { + return [ + { name: "assets", path: "assets", absolute: `${directory}/assets`, type: "directory", ignored: false }, + { name: "src", path: "src", absolute: `${directory}/src`, type: "directory", ignored: false }, + ] + } + if (path === "assets") { + return [ + { + name: "preview.png", + path: imageFile, + absolute: `${directory}/${imageFile}`, + type: "file", + ignored: false, + }, + ] + } + if (path === "src") { + return [ + { + name: "example.ts", + path: "src/example.ts", + absolute: `${directory}/src/example.ts`, + type: "file", + ignored: false, + }, + ] + } + return [] + }, + pageMessages: () => ({ + items: [ + { + info: { + id: "msg_review_image_flash_regression", + sessionID, + role: "user", + time: { created: 1700000000000 }, + summary: { diffs: [] }, + agent: "build", + model: { providerID: "opencode", modelID: "test" }, + }, + parts: [ + { + id: "prt_review_image_flash_regression", + sessionID, + messageID: "msg_review_image_flash_regression", + type: "text", + text: "Review this change.", + }, + ], + }, + ], + }), + }) + + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, title) + await page.getByRole("button", { name: "Toggle review" }).click() + await expectAppVisible(page.locator('#review-panel [data-component="session-review-v2"]')) + await expectAppVisible(page.getByRole("button", { name: /preview\.png/ })) +} + +async function installReviewFlashProbe(page: Page) { + await page.evaluate(() => { + const samples: Array<{ + observedAtMs: number + blank: boolean + blackCenter: boolean + text: string + background: string + }> = [] + const startedAt = performance.now() + const sample = () => { + const panel = document.querySelector('#review-panel [data-component="session-review-v2"]') + const rect = panel?.getBoundingClientRect() + const center = rect + ? document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2) + : undefined + const background = center instanceof Element ? getComputedStyle(center).backgroundColor : "" + samples.push({ + observedAtMs: performance.now() - startedAt, + blank: !panel || panel.textContent?.trim().length === 0, + blackCenter: background === "rgb(0, 0, 0)", + text: panel?.textContent?.trim().slice(0, 80) ?? "", + background, + }) + if (performance.now() - startedAt < 500) requestAnimationFrame(sample) + } + document.addEventListener( + "click", + (event) => { + const target = event.target instanceof Element ? event.target : undefined + if (!target?.closest('[data-slot="file-tree-v2-row"]')) return + requestAnimationFrame(sample) + }, + { capture: true, once: true }, + ) + ;(window as Window & { __reviewImageFlash?: { samples: typeof samples; startedAt: number } }).__reviewImageFlash = { + samples, + startedAt, + } + }) +} + +async function waitForReviewFlashProbe(page: Page, durationMs: number) { + await page.waitForFunction((durationMs) => { + const state = (window as Window & { __reviewImageFlash?: { samples: unknown[]; startedAt: number } }) + .__reviewImageFlash + return !!state && state.samples.length > 0 && performance.now() - state.startedAt >= durationMs + }, durationMs) +} + +async function collectReviewFlashProbe(page: Page) { + return page.evaluate(() => { + return (window as Window & { __reviewImageFlash?: { samples: unknown[]; startedAt: number } }).__reviewImageFlash! + }) as Promise<{ + startedAt: number + samples: Array<{ observedAtMs: number; blank: boolean; blackCenter: boolean; text: string; background: string }> + }> +} diff --git a/packages/app/e2e/regression/review-open-file.spec.ts b/packages/app/e2e/regression/review-open-file.spec.ts new file mode 100644 index 000000000000..397c53f248e5 --- /dev/null +++ b/packages/app/e2e/regression/review-open-file.spec.ts @@ -0,0 +1,155 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewOpenFile" +const projectID = "proj_review_open_file" +const sessionID = "ses_review_open_file" +const title = "Review open file" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("opens and searches project files inline", async ({ page }) => { + const searches: { query: string; dirs?: string; limit?: number }[] = [] + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "open-file-project", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [ + { + id: sessionID, + slug: sessionID, + projectID, + directory, + title, + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + }, + ], + vcsDiff: [fileDiff("src/changed.ts")], + fileList: (path) => { + if (path) return [] + return [ + fileNode("README.md"), + { name: "src", path: "src", absolute: `${directory}/src`, type: "directory", ignored: false }, + ] + }, + fileContent: (path) => ({ type: "text", content: `contents:${path}` }), + findFiles: (input) => { + searches.push(input) + return input.query === "nested" ? ["src/nested.ts"] : [] + }, + pageMessages: () => ({ items: [] }), + }) + await page.addInitScript( + ({ directory, server, sessionID }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.global.dat:layout", + JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }), + ) + localStorage.setItem( + "opencode.global.dat:review-panel-v2", + JSON.stringify({ sidebarOpened: false, sidebarWidth: 240, expandMode: "collapse" }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([{ type: "session", server, sessionId: sessionID }]), + ) + }, + { directory, server, sessionID }, + ) + + await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`) + await expectSessionTitle(page, title) + + const panel = page.locator("#review-panel") + const sidebar = panel.locator('[data-slot="session-review-v2-sidebar"]') + const contextButton = page.getByRole("button", { name: "View context usage" }) + await contextButton.click() + await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await expect(sidebar).toBeVisible() + await contextButton.click() + await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") + await expect(sidebar).toHaveCount(0) + await panel.getByRole("button", { name: "Open file" }).click() + const filter = panel.getByRole("combobox", { name: "Filter files" }) + await expect(filter).toBeFocused() + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("open-file-project", { exact: true })).toBeVisible() + + await panel.getByRole("button", { name: "README.md" }).click() + await expect(panel.getByRole("tab", { name: "README.md" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("contents:README.md", { exact: true })).toBeVisible() + await expect(sidebar).toHaveCount(0) + + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "README.md" })).toHaveCount(0) + await expect(sidebar).toBeVisible() + await filter.fill("nested") + const result = panel.getByRole("option", { name: /nested\.ts/ }) + await expect(result).toBeVisible() + const resultID = await result.getAttribute("id") + expect(resultID).toBeTruthy() + await expect(filter).toHaveAttribute("aria-activedescendant", resultID!) + await filter.press("Enter") + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("contents:src/nested.ts", { exact: true })).toBeVisible() + expect(searches).toContainEqual({ query: "nested", dirs: "false", limit: 200 }) + + await panel.getByRole("button", { name: "Open file" }).click() + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveCount(1) + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") + await page.keyboard.press("Control+w") + await expect(panel.getByRole("tab", { name: "Open file" })).toHaveCount(0) + await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "") +}) + +function fileNode(path: string) { + return { + name: path, + path, + absolute: `${directory}/${path}`, + type: "file", + ignored: false, + } +} + +function fileDiff(file: string) { + return { + file, + before: "before\n", + after: "after\n", + additions: 1, + deletions: 1, + status: "modified", + } +} diff --git a/packages/app/e2e/regression/review-state-persistence.spec.ts b/packages/app/e2e/regression/review-state-persistence.spec.ts new file mode 100644 index 000000000000..aa42f1bb516d --- /dev/null +++ b/packages/app/e2e/regression/review-state-persistence.spec.ts @@ -0,0 +1,152 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewStatePersistence" +const projectID = "proj_review_state_persistence" +const sessionA = "ses_review_state_a" +const sessionB = "ses_review_state_b" +const titleA = "Alpha review state" +const titleB = "Beta review state" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("restores review mode and selected file per session", async ({ page }) => { + await setup(page) + await page.goto(sessionHref(sessionA)) + await expectSessionTitle(page, titleA) + await page.getByRole("button", { name: "Toggle review" }).click() + + await selectMode(page, "Git changes", "Branch changes") + await selectFile(page, "beta.ts") + + await switchSession(page, titleB) + await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible() + await selectFile(page, "gamma.ts") + + await switchSession(page, titleA) + await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible() + await expectSelectedFile(page, "beta.ts") + await selectMode(page, "Branch changes", "Git changes") + await expectSelectedFile(page, "alpha.ts") + await selectMode(page, "Git changes", "Branch changes") + await expectSelectedFile(page, "beta.ts") + + await page.reload() + await expectSessionTitle(page, titleA) + await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible() + await expectSelectedFile(page, "beta.ts") + + await switchSession(page, titleB) + await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible() + await expectSelectedFile(page, "gamma.ts") +}) + +async function selectMode(page: Page, current: string, next: string) { + await page.getByRole("button", { name: current }).click() + await page.getByRole("option", { name: next }).click() +} + +async function selectFile(page: Page, file: string) { + await page.getByRole("button", { name: file }).click() + await expectSelectedFile(page, file) +} + +async function expectSelectedFile(page: Page, file: string) { + await expect(page.locator('[data-slot="session-review-v2-file-name"]')).toHaveText(file) +} + +async function switchSession(page: Page, title: string) { + await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click() + await expectSessionTitle(page, title) +} + +async function setup(page: Page) { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "review-state-persistence", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)], + pageMessages: () => ({ items: [] }), + }) + await page.route(/\/vcs(?:\?.*)?$/, (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ branch: "feature", default_branch: "dev" }), + }), + ) + await page.route("**/vcs/diff**", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify( + new URL(route.request().url()).searchParams.get("mode") === "branch" + ? [diff("src/alpha.ts"), diff("src/beta.ts")] + : [diff("src/alpha.ts"), diff("src/gamma.ts")], + ), + }), + ) + await page.addInitScript( + ({ directory, server, sessions }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))), + ) + }, + { directory, server, sessions: [sessionA, sessionB] }, + ) +} + +function session(id: string, title: string, created: number) { + return { + id, + slug: id, + projectID, + directory, + title, + version: "dev", + time: { created, updated: created }, + } +} + +function diff(file: string) { + return { + file, + additions: 1, + deletions: 1, + status: "modified", + patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`, + } +} + +function sessionHref(sessionID: string) { + return `/server/${base64Encode(server)}/session/${sessionID}` +} diff --git a/packages/app/e2e/regression/review-tab-switch.spec.ts b/packages/app/e2e/regression/review-tab-switch.spec.ts new file mode 100644 index 000000000000..c2ea406c5ab3 --- /dev/null +++ b/packages/app/e2e/regression/review-tab-switch.spec.ts @@ -0,0 +1,147 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectAppVisible, expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewTabSwitch" +const projectID = "proj_review_tab_switch" +const sessionA = "ses_review_tab_a" +const sessionB = "ses_review_tab_b" +const titleA = "Alpha session" +const titleB = "Beta session" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` +const diffs = Array.from({ length: 2_740 }, (_, index) => + fileDiff(`src/generated-${String(index).padStart(4, "0")}.ts`), +) +// Marks the review pane DOM node so a remount (fresh node) is detectable. +const PROBE = "original" + +test.use({ viewport: { width: 1440, height: 900 } }) + +// The v2 review pane's diff data is workspace-scoped: switching between session +// tabs in the same workspace must update its parameters reactively instead of +// tearing the pane down and remounting it (which flickers). +test("keeps the v2 review pane mounted when switching session tabs in a workspace", async ({ page }) => { + await setup(page) + + await page.goto(sessionHref(sessionA)) + await expectSessionTitle(page, titleA) + + await page.getByRole("button", { name: "Toggle review" }).click() + const reviewTab = page.getByRole("tab", { name: /Review/ }) + const reviewTabPanel = page.getByRole("tabpanel", { name: /Review/ }) + await expect(reviewTab).toHaveAttribute("aria-controls", "session-side-panel-review-tabpanel") + await expect(reviewTabPanel).toHaveAttribute("id", "session-side-panel-review-tabpanel") + const review = page.locator('#review-panel [data-component="session-review-v2"]') + await expectAppVisible(review) + await expectAppVisible(page.getByRole("button", { name: "generated-0000.ts" })) + await writeProbe(page) + + await switchTab(page, titleB) + await expectSessionTitle(page, titleB) + await expectAppVisible(review) + await expectAppVisible(page.getByRole("button", { name: "generated-0000.ts" })) + expect(await readProbe(page)).toBe(PROBE) + + await switchTab(page, titleA) + await expectSessionTitle(page, titleA) + await expectAppVisible(review) + await expectAppVisible(page.getByRole("button", { name: "generated-0000.ts" })) + expect(await readProbe(page)).toBe(PROBE) + + const viewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') + await viewport.hover() + await page.mouse.wheel(0, 100_000) + await expect + .poll(() => viewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) + .toBeLessThanOrEqual(1) + await expect(page.getByRole("button", { name: "generated-2739.ts" })).toBeVisible() +}) + +type Probed = HTMLElement & { __e2eProbe?: string } + +async function switchTab(page: Page, title: string) { + await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click() +} + +async function writeProbe(page: Page) { + await page.locator('#review-panel [data-component="session-review-v2"]').evaluate((el, probe) => { + ;(el as Probed).__e2eProbe = probe + }, PROBE) +} + +async function readProbe(page: Page) { + return page.locator('#review-panel [data-component="session-review-v2"]').evaluate((el) => (el as Probed).__e2eProbe) +} + +async function setup(page: Page) { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "review-tab-switch", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)], + vcsDiff: diffs, + pageMessages: () => ({ items: [] }), + }) + + await page.addInitScript( + ({ directory, server, sessions }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))), + ) + }, + { directory, server, sessions: [sessionA, sessionB] }, + ) +} + +function session(id: string, title: string, created: number) { + return { + id, + slug: id, + projectID, + directory, + title, + version: "dev", + time: { created, updated: created }, + } +} + +function sessionHref(sessionID: string) { + return `/server/${base64Encode(server)}/session/${sessionID}` +} + +function fileDiff(file: string) { + return { + file, + additions: 1, + deletions: 1, + status: "modified", + patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`, + } +} diff --git a/packages/app/e2e/regression/review-terminal-stacked.spec.ts b/packages/app/e2e/regression/review-terminal-stacked.spec.ts new file mode 100644 index 000000000000..e3ba607b3d84 --- /dev/null +++ b/packages/app/e2e/regression/review-terminal-stacked.spec.ts @@ -0,0 +1,263 @@ +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ReviewTerminalStacked" +const projectID = "proj_review_terminal_stacked" +const sessionID = "ses_review_terminal_stacked" +const title = "Review terminal stacked" +const branchDiffs = [ + fileDiff(".github/actions/setup-bun/action.yml", 7), + ...Array.from({ length: 2_739 }, (_, index) => + fileDiff( + `src/branch/d${String(Math.floor(index / 100)).padStart(5, "0")}/generated-${String(index).padStart(4, "0")}.ts`, + 100, + false, + ), + ), +] + +test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => { + test.setTimeout(120_000) + const events: Array<{ directory: string; payload: Record }> = [] + let detailVersion = 1 + let detailFailures = 1 + await page.setViewportSize({ width: 1400, height: 900 }) + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "review-terminal-stacked", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [ + { + id: sessionID, + slug: "review-terminal-stacked", + projectID, + directory, + title, + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + }, + ], + sessionStatus: { [sessionID]: { type: "idle" } }, + pageMessages: () => ({ items: [] }), + events: () => events.splice(0, 1), + eventRetry: 16, + }) + await page.route(/\/vcs(?:\?.*)?$/, (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ branch: "review-pane-performance", default_branch: "dev" }), + }), + ) + await page.route("**/vcs/diff**", (route) => { + const url = new URL(route.request().url()) + const scope = url.searchParams.get("directory")?.replaceAll("\\", "/") + const detail = scope?.endsWith("/src/branch/d00027") + if (detail && detailFailures-- > 0) return route.fulfill({ status: 500, body: "retry detail" }) + return route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify( + url.searchParams.get("mode") === "branch" + ? detail + ? branchDiffs + .filter((diff) => diff.file.startsWith("src/branch/d00027/")) + .map((diff) => fileDiff(diff.file, diff.additions, true, detailVersion)) + : branchDiffs + : Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)), + ), + }) + }) + await page.route("**/pty", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ id: "pty_review_terminal", title: "Terminal 1" }), + }), + ) + await page.route("**/pty/pty_review_terminal", (route) => + route.fulfill({ status: 200, contentType: "application/json", body: "{}" }), + ) + await page.routeWebSocket("**/pty/pty_review_terminal/connect", () => undefined) + await page.addInitScript(() => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:layout", + JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }), + ) + }) + + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, title) + await expect(page.locator("#review-panel")).toBeVisible() + await expectTree(page, 8, "git-0.ts") + + await selectMode(page, "Git changes", "Branch changes") + await expect(page.getByRole("tab", { name: "Review 2740" })).toBeVisible() + await page.keyboard.press("Control+Backquote") + await expect(page.locator("#terminal-panel")).toBeVisible() + await expectTree(page, 2_773, "action.yml") + await expectStackGeometry(page) + + const treeViewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') + await treeViewport.hover() + await page.mouse.wheel(0, 100_000) + await expect + .poll(() => treeViewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) + .toBeLessThanOrEqual(1) + const lastFile = page.getByRole("button", { name: "generated-2738.ts" }) + await expect(lastFile).toBeVisible() + const bottomGap = await lastFile.evaluate((element) => { + const viewport = element.closest(".scroll-view__viewport")!.getBoundingClientRect() + return viewport.bottom - element.getBoundingClientRect().bottom + }) + expect(bottomGap).toBeGreaterThanOrEqual(0) + expect(bottomGap).toBeLessThanOrEqual(16) + const lazyDiff = page.waitForRequest((request) => { + const url = new URL(request.url()) + return ( + url.pathname === "/vcs/diff" && + url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true + ) + }) + await lastFile.click() + await lazyDiff + const preview = page.locator('[data-slot="session-review-v2-diff-scroll"]') + await expect(preview).toContainText("after-1") + detailVersion = 2 + events.push(statusEvent("busy")) + await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() + const refreshedDiff = page.waitForRequest((request) => { + const url = new URL(request.url()) + return ( + url.pathname === "/vcs/diff" && + url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true + ) + }) + events.push(statusEvent("idle")) + await refreshedDiff + await expect(preview).toContainText("after-2") + await selectMode(page, "Branch changes", "Git changes") + await expectTree(page, 8, "git-0.ts") + await page.getByRole("button", { name: "git-0.ts" }).click() + await selectMode(page, "Git changes", "Branch changes") + await expectTree(page, 2_773, "action.yml") + + const filter = page.getByRole("searchbox", { name: "Filter files" }) + await filter.fill("generated-2738") + await expectTree(page, 1, "generated-2738.ts") + await filter.fill("") + await expectTree(page, 2_773, "action.yml") + + await page.getByRole("button", { name: "Toggle file tree" }).click() + await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveCount(0) + await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(0) + await page.getByRole("button", { name: "Toggle file tree" }).click() + await expectTree(page, 2_773, "action.yml") + + await page.keyboard.press("Control+Backquote") + await expect(page.locator("#terminal-panel")).toHaveCount(0) + await expectTree(page, 2_773, "action.yml") + await page.keyboard.press("Control+Backquote") + await expect(page.locator("#terminal-panel")).toBeVisible() + await expectTree(page, 2_773, "action.yml") + + await page.getByRole("button", { name: "Toggle review" }).click() + await expect(page.locator("#review-panel")).toHaveCount(0) + await page.getByRole("button", { name: "Toggle review" }).click() + await expectTree(page, 2_773, "action.yml") + await page.setViewportSize({ width: 1_000, height: 700 }) + await expectTree(page, 2_773, "action.yml") + await expectStackGeometry(page) + await page.setViewportSize({ width: 1_000, height: 120 }) + await page.setViewportSize({ width: 1_400, height: 900 }) + await expectTree(page, 2_773, "action.yml") + await expectStackGeometry(page) +}) + +async function selectMode(page: Page, current: string, next: string) { + await page.getByRole("button", { name: current }).click() + const option = page.getByRole("option", { name: next }) + await expect(option).toBeVisible() + await option.click() +} + +async function expectTree(page: Page, total: number, file: string) { + await expectMountedTree(page, total) + await expect(page.getByRole("button", { name: file })).toBeVisible() +} + +async function expectMountedTree(page: Page, total: number) { + const tree = page.locator('#review-panel [data-component="file-tree-v2"]') + await expect(tree).toHaveAttribute("data-total-rows", String(total)) + await expect + .poll(() => tree.evaluate((element) => element.querySelectorAll('[data-slot="file-tree-v2-row"]').length)) + .toBeGreaterThan(0) + const state = await tree.evaluate((element) => ({ + root: element.getBoundingClientRect().height, + viewport: element.closest(".scroll-view__viewport")!.getBoundingClientRect().height, + rows: element.querySelectorAll('[data-slot="file-tree-v2-row"]').length, + })) + expect(state.viewport).toBeGreaterThan(0) + expect(state.root).toBeGreaterThan(0) + expect(state.rows).toBeGreaterThan(0) + expect(state.rows).toBeLessThanOrEqual(60) +} + +async function expectStackGeometry(page: Page) { + const geometry = await page.evaluate(() => { + const review = document.querySelector("#review-panel")! + const terminal = document.querySelector("#terminal-panel")! + const reviewParent = review.parentElement!.getBoundingClientRect() + const terminalParent = terminal.parentElement!.getBoundingClientRect() + return { + review: review.getBoundingClientRect().height, + reviewParent: reviewParent.height, + terminal: terminal.getBoundingClientRect().height, + terminalParent: terminalParent.height, + } + }) + expect(Math.abs(geometry.review - geometry.reviewParent)).toBeLessThanOrEqual(1) + expect(Math.abs(geometry.terminal - geometry.terminalParent)).toBeLessThanOrEqual(1) +} + +function base64Encode(value: string) { + return Buffer.from(value, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") +} + +function statusEvent(type: "busy" | "idle") { + return { + directory, + payload: { type: "session.status", properties: { sessionID, status: { type } } }, + } +} + +function fileDiff(file: string, additions: number, loaded = true, version = 1) { + return { + file, + additions, + deletions: 0, + status: "modified", + patch: loaded + ? `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after-${version}'\n` + : `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}`, + } +} diff --git a/packages/app/e2e/regression/session-request-docks.spec.ts b/packages/app/e2e/regression/session-request-docks.spec.ts index e66cf81504ee..036eaaef4221 100644 --- a/packages/app/e2e/regression/session-request-docks.spec.ts +++ b/packages/app/e2e/regression/session-request-docks.spec.ts @@ -38,6 +38,29 @@ test("shows a pending question dock", async ({ page }) => { await expect(question.getByRole("radio", { name: /Extended/ })).toBeVisible() await expect(page.locator('[data-component="session-composer"]')).toHaveCount(0) + const rejectRequests: string[] = [] + page.on("request", (request) => { + if (request.method() !== "POST") return + if (new URL(request.url()).pathname === "/question/question-request/reject") rejectRequests.push(request.url()) + }) + + await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click() + await expect(question).toBeVisible() + await expect(question.getByText("Which implementation should be used?")).toBeVisible() + await expect(question.getByText("Select one answer")).toBeHidden() + await expect(question.getByRole("radio", { name: /Minimal/ })).toBeHidden() + await expect(question.getByRole("radio", { name: /Extended/ })).toBeHidden() + await expect(question.getByRole("button", { name: "Dismiss" })).toBeVisible() + await expect(question.getByRole("button", { name: "Submit" })).toBeVisible() + await expect(page.locator('[data-component="question-minimized-dock"]')).toHaveCount(0) + expect(rejectRequests).toEqual([]) + + await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click() + await expect(question).toBeVisible() + await expect(question.getByText("Which implementation should be used?")).toBeVisible() + await expect(question.getByRole("radio", { name: /Minimal/ })).toBeVisible() + expect(rejectRequests).toEqual([]) + await question.getByRole("radio", { name: /Minimal/ }).click() const reply = page.waitForRequest( (request) => request.method() === "POST" && new URL(request.url()).pathname === "/question/question-request/reply", diff --git a/packages/app/e2e/regression/session-timeline-accessibility.spec.ts b/packages/app/e2e/regression/session-timeline-accessibility.spec.ts new file mode 100644 index 000000000000..598763c02253 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-accessibility.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from "@playwright/test" +import { assistantMessage, setupTimeline, shell, userMessage } from "../performance/timeline-stability/fixture" + +test("space activates a focused timeline button instead of scrolling", async ({ page }) => { + const shellID = "prt_space_button_shell" + await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(shellID, "completed", lines(5))])], + settings: { shellToolPartsExpanded: false }, + reducedMotion: true, + }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + const trigger = page.locator(`[data-timeline-part-id="${shellID}"] [data-slot="collapsible-trigger"]`) + await trigger.focus() + const before = await scroller.evaluate((element) => element.scrollTop) + await trigger.press("Space") + await expect(trigger).toHaveAttribute("aria-expanded", "true") + expect(await scroller.evaluate((element) => element.scrollTop)).toBe(before) +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} diff --git a/packages/app/e2e/regression/session-timeline-context-resize.spec.ts b/packages/app/e2e/regression/session-timeline-context-resize.spec.ts index 9ed5906bb045..a9a4738da928 100644 --- a/packages/app/e2e/regression/session-timeline-context-resize.spec.ts +++ b/packages/app/e2e/regression/session-timeline-context-resize.spec.ts @@ -1,6 +1,13 @@ import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible, expectSessionTitle } from "../utils/waits" +import { + analyzeVisualObservations, + defineVisualRegions, + startVisualProbe, + stopVisualProbe, + visualPlan, +} from "../utils/visual-stability" const directory = "C:/OpenCode/ContextResizeRegression" const projectID = "proj_context_resize_regression" @@ -36,6 +43,82 @@ test.describe("regression: session timeline context group resize", () => { expect(visibleOverlap).toEqual([]) expect(samples.at(-1)?.expanded).toBe("true") }) + + test("paints a stable exploring to explored transition", async ({ page }) => { + const events: { directory: string; payload: Record }[] = [] + await page.setViewportSize({ width: 1400, height: 900 }) + await mockServer(page, events, [ + ...Array.from({ length: 8 }, (_, index) => turn(index, false)).flat(), + ...turn(10, true, "running"), + ]) + await configurePage(page) + + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, title) + const devtools = await page.context().newCDPSession(page) + await devtools.send("Emulation.setCPUThrottlingRate", { rate: 4 }) + const context = page.locator(`[data-timeline-part-ids="${contextIDs.join(",")}"]`).first() + await expectAppVisible(context) + await expect(context.locator('[data-component="tool-status-title"]')).toHaveAttribute("aria-label", "Exploring") + + const contextSelector = `[data-timeline-part-ids="${contextIDs.join(",")}"]` + const regions = defineVisualRegions({ + status: { + selector: `${contextSelector} [data-component="tool-status-title"]`, + opacitySelectors: ['[data-slot="tool-status-active"]', '[data-slot="tool-status-done"]'], + }, + context: { selector: contextSelector, closest: '[data-timeline-row="AssistantPart"]' }, + following: { + selector: `[data-timeline-part-id="${followingTextID}"]`, + closest: '[data-timeline-row="AssistantPart"]', + }, + }) + await startVisualProbe(page, regions) + for (const [index, delay] of [120, 350, 80, 500].entries()) { + events.push({ + directory, + payload: { + type: "message.part.updated", + properties: { + part: contextTool( + contextIDs[index]!, + id("msg_assistant", 10), + ["read", "glob", "grep", "list"][index]!, + [ + { filePath: "src/recent-a.ts" }, + { path: directory, pattern: "**/*.ts" }, + { path: directory, pattern: "Explored" }, + { path: "src" }, + ][index]!, + ), + }, + }, + }) + await page.waitForTimeout(delay) + } + + await expect(context.locator('[data-component="tool-status-title"]')).toHaveAttribute("aria-label", "Explored") + await page.waitForTimeout(700) + const trace = await stopVisualProbe(page) + const labels = trace.samples + .map((sample) => sample.regions.status?.label) + .filter((value): value is string => !!value) + .filter((value, index, all) => value !== all[index - 1]) + const issues = analyzeVisualObservations( + trace.samples, + visualPlan(regions, [ + { type: "required", regions: ["context", "following"] }, + { type: "opacity", regions: "all" }, + { type: "continuity", regions: "all" }, + { type: "motion", regions: "all" }, + { type: "label-stability", regions: "all" }, + { type: "flow", regions: ["context", "following"] }, + ]), + ) + + expect(labels).toEqual(["Exploring", "Explored"]) + expect(issues, JSON.stringify(trace.samples, null, 2)).toEqual([]) + }) }) async function configurePage(page: Page) { @@ -128,7 +211,7 @@ async function sampleExpansion(page: Page) { ) } -function turn(index: number, target: boolean): Message[] { +function turn(index: number, target: boolean, status: "running" | "completed" = "completed"): Message[] { const userID = id("msg_user", index) const assistantID = id("msg_assistant", index) return [ @@ -163,10 +246,22 @@ function turn(index: number, target: boolean): Message[] { }, parts: target ? [ - contextTool(contextIDs[0]!, assistantID, "read", { filePath: "src/recent-a.ts", offset: 0, limit: 120 }), - contextTool(contextIDs[1]!, assistantID, "glob", { path: directory, pattern: "**/*.ts" }), - contextTool(contextIDs[2]!, assistantID, "grep", { path: directory, pattern: "Explored", include: "*.ts" }), - contextTool(contextIDs[3]!, assistantID, "list", { path: "src" }), + contextTool( + contextIDs[0]!, + assistantID, + "read", + { filePath: "src/recent-a.ts", offset: 0, limit: 120 }, + status, + ), + contextTool(contextIDs[1]!, assistantID, "glob", { path: directory, pattern: "**/*.ts" }, status), + contextTool( + contextIDs[2]!, + assistantID, + "grep", + { path: directory, pattern: "Explored", include: "*.ts" }, + status, + ), + contextTool(contextIDs[3]!, assistantID, "list", { path: "src" }, status), { id: followingTextID, sessionID, @@ -188,7 +283,13 @@ function turn(index: number, target: boolean): Message[] { ] } -function contextTool(partID: string, messageID: string, tool: string, input: Record) { +function contextTool( + partID: string, + messageID: string, + tool: string, + input: Record, + status: "running" | "completed" = "completed", +) { return { id: partID, sessionID, @@ -197,7 +298,7 @@ function contextTool(partID: string, messageID: string, tool: string, input: Rec callID: `call_${partID}`, tool, state: { - status: "completed", + status, input, output: `Completed ${tool}.\n${"detail line\n".repeat(8)}`, title: input.filePath || input.path || input.pattern || "completed", @@ -207,13 +308,19 @@ function contextTool(partID: string, messageID: string, tool: string, input: Rec } } -async function mockServer(page: Page) { +async function mockServer( + page: Page, + events: { directory: string; payload: Record }[] = [], + fixtureMessages = messages, +) { await mockOpenCodeServer(page, { directory, project: project(), provider: provider(), sessions: [session()], - pageMessages: () => ({ items: messages }), + pageMessages: () => ({ items: fixtureMessages }), + events: () => events.splice(0, 1), + eventRetry: 50, }) } diff --git a/packages/app/e2e/regression/session-timeline-context-state.spec.ts b/packages/app/e2e/regression/session-timeline-context-state.spec.ts new file mode 100644 index 000000000000..37878325e14d --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-context-state.spec.ts @@ -0,0 +1,31 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + partUpdated, + setupTimeline, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("preserves a collapsed context group through count and status updates", async ({ page }) => { + const ids = ["prt_closed_01_read", "prt_closed_02_glob"] + const inputs = { + read: { filePath: "src/a.ts", offset: 0, limit: 120 }, + glob: { path: ".", pattern: "**/*.ts" }, + } + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [toolPart(ids[0]!, "read", "running", inputs.read), toolPart(ids[1]!, "glob", "running", inputs.glob)], + { completed: false }, + ), + ], + }) + const group = page.locator(`[data-timeline-part-ids="${ids.join(",")}"]`) + const trigger = group.locator('[data-slot="collapsible-trigger"]') + await expect(trigger).toHaveAttribute("aria-expanded", "false") + await timeline.send(partUpdated(toolPart(ids[0]!, "read", "completed", inputs.read)), 100) + await timeline.send(partUpdated(toolPart(ids[1]!, "glob", "completed", inputs.glob)), 300) + await expect(trigger).toHaveAttribute("aria-expanded", "false") +}) diff --git a/packages/app/e2e/regression/session-timeline-file-projection.spec.ts b/packages/app/e2e/regression/session-timeline-file-projection.spec.ts new file mode 100644 index 000000000000..a591ff9470f6 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-file-projection.spec.ts @@ -0,0 +1,52 @@ +import { expect, test } from "@playwright/test" +import { assistantMessage, setupTimeline, toolPart, userMessage } from "../performance/timeline-stability/fixture" + +test("renders completed write content", async ({ page }) => { + const id = "prt_file_projection_write" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(id, "write", "completed", { filePath: "src/write.ts", content: "export const written = true\n" }), + ]), + ], + settings: { editToolPartsExpanded: true }, + }) + + await expect(page.locator(`[data-timeline-part-id="${id}"] [data-component="write-content"]`)).toBeVisible() +}) + +test("renders a completed single-file patch", async ({ page }) => { + const id = "prt_file_projection_single_patch" + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart( + id, + "patch", + "completed", + { files: ["src/a.ts"] }, + { + metadata: { + files: [ + { + filePath: "src/a.ts", + relativePath: "src/a.ts", + type: "update", + additions: 1, + deletions: 1, + before: "export const value = 1\n", + after: "export const value = 2\n", + }, + ], + }, + }, + ), + ]), + ], + settings: { editToolPartsExpanded: true }, + }) + + await expect(page.locator(`[data-timeline-part-id="${id}"] [data-component="apply-patch-file-diff"]`)).toBeVisible() +}) diff --git a/packages/app/e2e/regression/session-timeline-file-state.spec.ts b/packages/app/e2e/regression/session-timeline-file-state.spec.ts new file mode 100644 index 000000000000..f0871a0da3e4 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-file-state.spec.ts @@ -0,0 +1,94 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + partUpdated, + setupTimeline, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("updates edit diagnostics without resetting manual collapse state", async ({ page }) => { + const editID = "prt_diagnostics_edit" + const base = editPart(editID, []) + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([base])], + settings: { editToolPartsExpanded: true }, + }) + const trigger = page.locator(`[data-timeline-part-id="${editID}"] [data-slot="collapsible-trigger"]`).first() + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "false") + await timeline.send( + partUpdated(editPart(editID, [diagnostic("First failure", 2), diagnostic("Second failure", 4)])), + 300, + ) + await expect(trigger).toHaveAttribute("aria-expanded", "false") + await timeline.send(partUpdated(editPart(editID, [])), 300) + await expect(trigger).toHaveAttribute("aria-expanded", "false") +}) + +test("preserves nested patch file state through outer collapse and reopen", async ({ page }) => { + const patchID = "prt_nested_patch" + const files = [patchFile("src/a.ts", "update"), patchFile("src/b.ts", "add"), patchFile("src/old.ts", "delete")] + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart( + patchID, + "patch", + "completed", + { files: files.map((file) => file.filePath) }, + { metadata: { files } }, + ), + ]), + ], + settings: { editToolPartsExpanded: true }, + }) + const wrapper = page.locator(`[data-timeline-part-id="${patchID}"]`) + const outer = wrapper.locator('[data-slot="collapsible-trigger"]').first() + const deleted = wrapper.locator('[data-scope="apply-patch"] [data-type="delete"]') + await deleted.getByRole("button").click() + await expect(deleted.getByRole("button")).toHaveAttribute("aria-expanded", "true") + await outer.click() + await expect(outer).toHaveAttribute("aria-expanded", "false") + await outer.click() + await expect(outer).toHaveAttribute("aria-expanded", "true") + await expect(deleted.getByRole("button")).toHaveAttribute("aria-expanded", "true") +}) + +function patchFile(filePath: string, type: "add" | "update" | "delete") { + return { + filePath, + relativePath: filePath, + type, + additions: type === "delete" ? 0 : 4, + deletions: type === "add" ? 0 : 3, + before: type === "add" ? undefined : source(false), + after: type === "delete" ? undefined : source(true), + } +} + +function editPart(id: string, diagnostics: Record[]) { + return toolPart( + id, + "edit", + "completed", + { filePath: "src/edit.ts" }, + { + metadata: { + filediff: { file: "src/edit.ts", additions: 1, deletions: 1, before: source(false), after: source(true) }, + diagnostics, + }, + }, + ) +} + +function diagnostic(message: string, line: number) { + return { message, severity: 1, range: { start: { line, character: 0 }, end: { line, character: 2 } } } +} + +function source(changed: boolean) { + return Array.from({ length: 12 }, (_, index) => `export const value${index} = ${changed ? index + 1 : index}\n`).join( + "", + ) +} diff --git a/packages/app/e2e/regression/session-timeline-history-root.spec.ts b/packages/app/e2e/regression/session-timeline-history-root.spec.ts new file mode 100644 index 000000000000..15375cafed59 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-history-root.spec.ts @@ -0,0 +1,220 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { + assistantMessage, + directory, + messageUpdated, + project, + session, + sessionID, + status, + textPart, + title, + userID, + userMessage, +} from "../performance/timeline-stability/fixture" +import { mockOpenCodeServer } from "../utils/mock-server" +import { installSseTransport } from "../utils/sse-transport" +import { expectSessionTitle } from "../utils/waits" + +const assistants = Array.from({ length: 14 }, (_, index) => + assistantMessage([textPart(`prt_history_root_${index}`, `Assistant response ${index}`)], { + id: `msg_${String(index + 1001).padStart(4, "0")}_history_root_assistant`, + parentID: userID, + created: 1700000001000 + index * 1_000, + completed: index < 13, + }), +) +const messages = [userMessage(), ...assistants] +const lastAssistant = assistants.at(-1)! +const lastPartID = assistants.at(-1)!.parts[0]!.id +const userPartID = `prt_${userID}_text` +const completed = { + ...lastAssistant.info, + time: { ...lastAssistant.info.time, completed: lastAssistant.info.time.created + 15_000 }, +} +const scenarios = [ + { name: "completion", info: completed, idleFirst: false, interrupted: false }, + { + name: "interruption", + info: { ...completed, error: { name: "MessageAbortedError", data: { message: "Stopped" } } }, + idleFirst: true, + interrupted: true, + }, +] as const + +test.use({ viewport: { width: 646, height: 1385 } }) + +for (const scenario of scenarios) { + test(`keeps the latest user turn visible through ${scenario.name}`, async ({ page }) => { + const requests: { before?: string; phase: "start" | "end" }[] = [] + const pages: { before?: string; limit: number }[] = [] + const roots: { sessionID: string; messageID: string }[] = [] + const sequence: string[] = [] + const history = Promise.withResolvers() + const transport = await installSseTransport<{ directory: string; payload: Record }>(page, { + server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + retry: 20, + }) + await mockOpenCodeServer(page, { + directory, + project: project(), + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { + "claude-opus-4-6": { + id: "claude-opus-4-6", + name: "Claude Opus 4.6", + limit: { context: 200_000 }, + }, + }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "claude-opus-4-6" }, + }, + sessions: [session()], + sessionStatus: { [sessionID]: { type: "busy" } }, + beforeMessagesResponse: (request) => (request.before ? history.promise : Promise.resolve()), + onMessages: (request) => { + requests.push(request) + sequence.push(`messages:${request.phase}:${request.before ?? "latest"}`) + }, + onMessage: (request) => { + roots.push(request) + sequence.push(`message:${request.messageID}`) + }, + message: (requestedSessionID, messageID) => { + if (requestedSessionID !== sessionID) return + return messages.find((item) => item.info.id === messageID) + }, + pageMessages: (_, limit, before) => { + pages.push({ before, limit }) + const end = before ? messages.findIndex((message) => message.info.id === before) : messages.length + const start = Math.max(0, end - limit) + return { + items: messages.slice(start, end), + cursor: start > 0 ? messages[start]!.info.id : undefined, + } + }, + }) + await page.addInitScript( + ({ userPartID, lastPartID }) => { + const state = { armed: false, hidden: false, samples: 0, stop: false } + ;(window as Window & { __historyRootProbe?: typeof state }).__historyRootProbe = state + const sample = () => { + if (state.armed) { + const virtual = document.querySelector("[data-timeline-virtual-content]") + const viewport = virtual?.closest(".scroll-view__viewport") + const view = viewport?.getBoundingClientRect() + const visible = (partID: string) => { + const part = viewport?.querySelector(`[data-timeline-part-id="${partID}"]`) + const rect = part?.getBoundingClientRect() + return ( + !!rect && + !!view && + rect.width > 0 && + rect.height > 0 && + rect.bottom > view.top && + rect.top < view.bottom + ) + } + if (!virtual || !visible(userPartID) || !visible(lastPartID)) state.hidden = true + state.samples++ + } + if (!state.stop) requestAnimationFrame(() => setTimeout(sample, 0)) + } + requestAnimationFrame(() => setTimeout(sample, 0)) + }, + { userPartID, lastPartID }, + ) + + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await transport.waitForConnection() + await expectSessionTitle(page, title) + await expect(page.locator(`[data-timeline-part-id="${lastPartID}"]`)).toBeVisible() + await expect(page.locator(`[data-timeline-part-id="${userPartID}"]`)).toBeVisible() + await expect.poll(() => requests.filter((request) => request.phase === "start").length).toBe(2) + expect(requests.filter((request) => request.phase === "end")).toHaveLength(1) + expect(sequence.slice(0, 4)).toEqual([ + "messages:start:latest", + "messages:end:latest", + `message:${userID}`, + `messages:start:${messages.at(-2)!.info.id}`, + ]) + await page.evaluate(() => { + ;( + window as Window & { + __historyRootProbe?: { armed: boolean } + } + ).__historyRootProbe!.armed = true + }) + await waitForProbeSamples(page, 0) + expect(await historyRootHidden(page)).toBe(false) + const beforeHistory = await probeSamples(page) + history.resolve() + await expect(page.locator('[data-timeline-part-id^="prt_history_root_"]')).toHaveCount(14) + await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() + await waitForProbeSamples(page, beforeHistory) + expect(pages[0]).toEqual({ before: undefined, limit: 2 }) + expect(roots).toEqual([{ sessionID, messageID: userID }]) + + const message = messageUpdated(scenario.info) + const idle = status("idle") + for (const event of scenario.idleFirst ? [idle, message] : [message, idle]) { + const beforeEvent = await probeSamples(page) + await transport.send(event) + if (event === idle) await expect(page.getByRole("button", { name: "Stop" })).toHaveCount(0) + if (event === message && scenario.interrupted) + await expect(page.getByText("Interrupted", { exact: true })).toBeVisible() + await waitForProbeSamples(page, beforeEvent) + const current = await timelineState(page) + expect(current, JSON.stringify(current)).toMatchObject({ virtual: true }) + expect(current.rows, JSON.stringify(current)).toBeGreaterThan(0) + } + + expect(requests[0]).toEqual({ before: undefined, phase: "start", sessionID }) + expect(requests[1]).toEqual({ before: undefined, phase: "end", sessionID }) + await expect(page.getByRole("button", { name: "Stop" })).toHaveCount(0) + await expect(page.locator('[data-timeline-row="bottom-spacer"]')).toBeVisible() + if (scenario.interrupted) await expect(page.getByText("Interrupted", { exact: true })).toBeVisible() + expect( + await page.evaluate(() => { + const state = (window as Window & { __historyRootProbe?: { hidden: boolean; stop: boolean } }) + .__historyRootProbe! + state.stop = true + return state.hidden + }), + ).toBe(false) + }) +} + +function timelineState(page: Page) { + return page.evaluate(() => ({ + virtual: !!document.querySelector("[data-timeline-virtual-content]"), + rows: document.querySelectorAll("[data-timeline-key]").length, + })) +} + +function probeSamples(page: Page) { + return page.evaluate( + () => (window as Window & { __historyRootProbe?: { samples: number } }).__historyRootProbe!.samples, + ) +} + +async function waitForProbeSamples(page: Page, after: number) { + await page.waitForFunction( + (after) => + (window as Window & { __historyRootProbe?: { samples: number } }).__historyRootProbe!.samples >= after + 3, + after, + ) +} + +function historyRootHidden(page: Page) { + return page.evaluate( + () => (window as Window & { __historyRootProbe?: { hidden: boolean } }).__historyRootProbe!.hidden, + ) +} diff --git a/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts b/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts new file mode 100644 index 000000000000..3e2b171bca0a --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts @@ -0,0 +1,94 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + completedAssistantInfo, + messageUpdated, + partUpdated, + reasoningPart, + setupTimeline, + shell, + status, + textPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +for (const expanded of [false, true]) { + test(`preserves shell user intent from a ${expanded ? "expanded" : "collapsed"} default`, async ({ page }) => { + const id = `prt_shell_default_${expanded}` + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(id, "completed", lines(3))])], + settings: { shellToolPartsExpanded: expanded }, + }) + const trigger = page.locator(`[data-timeline-part-id="${id}"] [data-slot="collapsible-trigger"]`) + await expect(trigger).toHaveAttribute("aria-expanded", String(expanded)) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", String(!expanded)) + + await timeline.send(partUpdated(shell(id, "completed", lines(6))), 180) + await timeline.send(partUpdated(textPart(`prt_sibling_${expanded}`, "Sibling content")), 180) + await timeline.send(status("busy"), 100) + await timeline.send(status("idle"), 250) + await expect(trigger).toHaveAttribute("aria-expanded", String(!expanded)) + }) +} + +test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => { + const reasoningID = "prt_reasoning_hidden" + const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false }) + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistant], + settings: { showReasoningSummaries: false }, + cpuRate: 4, + }) + await timeline.send(status("busy"), 150) + + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await expect(page.getByText("Inspecting stability", { exact: true })).toBeVisible() + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0) + await timeline.send(partUpdated(shell("prt_reasoning_shell", "running")), 160) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await timeline.send(partUpdated(shell("prt_reasoning_shell", "completed", "done")), 180) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100) + await timeline.send(status("idle"), 300) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0) +}) + +test("moves busy through retry and recovery to final idle content", async ({ page }) => { + const assistant = assistantMessage([], { completed: false }) + const timeline = await setupTimeline(page, { + messages: [ + userMessage(undefined, { + summary: { + diffs: [ + { + file: "src/retry.ts", + additions: 1, + deletions: 1, + patch: "@@ -1 +1 @@\n-export const retry = false\n+export const retry = true", + }, + ], + }, + }), + assistant, + ], + }) + await timeline.send(status("busy"), 140) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0) + await timeline.send(status("retry"), 180) + await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await timeline.send(status("busy", 2), 180) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100) + await timeline.send(status("idle"), 350) + await expect(page.locator('[data-timeline-row="Retry"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toBeVisible() +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} diff --git a/packages/app/e2e/regression/session-timeline-locale-projection.spec.ts b/packages/app/e2e/regression/session-timeline-locale-projection.spec.ts new file mode 100644 index 000000000000..3901f8865e3f --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-locale-projection.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from "@playwright/test" +import { assistantMessage, setupTimeline, toolPart, userMessage } from "../performance/timeline-stability/fixture" + +for (const profile of [ + { locale: "de", label: "Erkundet" }, + { locale: "ar", label: "تم الاستكشاف" }, +] as const) { + test(`projects translated context status in ${profile.locale}`, async ({ page }) => { + const ids = [`prt_locale_${profile.locale}_01_read`, `prt_locale_${profile.locale}_02_glob`] + await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(ids[0]!, "read", "completed", { filePath: "src/a.ts" }), + toolPart(ids[1]!, "glob", "completed", { path: ".", pattern: "**/*.ts" }), + ]), + ], + locale: profile.locale, + }) + + const group = page.locator(`[data-timeline-part-ids="${ids.join(",")}"]`) + await expect(group.locator('[data-component="tool-status-title"]')).toHaveAttribute("aria-label", profile.label) + await expect(page.locator("html")).toHaveAttribute("lang", profile.locale) + }) +} diff --git a/packages/app/e2e/regression/session-timeline-projection.spec.ts b/packages/app/e2e/regression/session-timeline-projection.spec.ts new file mode 100644 index 000000000000..45304df365ff --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-projection.spec.ts @@ -0,0 +1,282 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + setupTimeline, + status, + toolPart, + userMessage, + userText, + type PartSeed, +} from "../performance/timeline-stability/fixture" + +test.describe("session timeline projection", () => { + test("renders every admitted tool family and hides timeline-only exclusions", async ({ page }) => { + const parts = [ + toolPart("prt_01_read", "read", "completed", { filePath: "src/a.ts" }), + toolPart("prt_02_glob", "glob", "completed", { path: ".", pattern: "**/*.ts" }), + toolPart("prt_03_grep", "grep", "completed", { path: ".", pattern: "value" }), + toolPart("prt_04_list", "list", "completed", { path: "src" }), + toolPart("prt_webfetch", "webfetch", "completed", { url: "https://example.com" }), + toolPart( + "prt_websearch", + "websearch", + "completed", + { query: "timeline stability" }, + { output: "https://example.com/result" }, + ), + toolPart("prt_task", "task", "completed", { description: "Inspect timeline", subagent_type: "explore" }), + toolPart( + "prt_bash", + "bash", + "completed", + { command: "printf stable" }, + { output: "stable", title: "printf stable" }, + ), + editPart("prt_edit"), + toolPart("prt_write", "write", "completed", { filePath: "src/new.ts", content: "export const stable = true\n" }), + patchPart("prt_patch"), + toolPart( + "prt_question", + "question", + "completed", + { questions: [{ question: "Keep stable?", header: "Stability", options: [] }] }, + { metadata: { answers: [["Yes"]] } }, + ), + toolPart("prt_skill", "skill", "completed", { name: "stability" }), + toolPart("prt_custom", "custom_mcp_tool", "completed", { target: "timeline", count: 2 }), + ] + await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] }) + + await expect( + page.locator('[data-timeline-part-ids="prt_01_read,prt_02_glob,prt_03_grep,prt_04_list"]'), + ).toBeVisible() + for (const id of [ + "prt_webfetch", + "prt_websearch", + "prt_task", + "prt_bash", + "prt_edit", + "prt_write", + "prt_patch", + "prt_question", + "prt_skill", + "prt_custom", + ]) { + await expect(page.locator(`[data-timeline-part-id="${id}"]`).first(), id).toBeVisible() + } + }) + + test("projects gaps, dividers, assistant parts, and errors together", async ({ page }) => { + const firstUser = userMessage( + [ + userText("The user made the following comment regarding lines 4 through 8 of src/a.ts: Keep this stable", { + id: "prt_comment", + synthetic: true, + metadata: { + opencodeComment: { + path: "src/a.ts", + selection: { startLine: 4, startChar: 0, endLine: 8, endChar: 0 }, + comment: "Keep this stable", + }, + }, + }), + userText("Continue after the comment", { id: "prt_visible_user" }), + ], + { summary: { diffs: Array.from({ length: 11 }, (_, index) => summaryDiff(index)) } }, + ) + const aborted = assistantMessage( + [ + { id: "prt_before_abort", type: "text", text: "Before interruption" }, + { id: "prt_compaction", type: "compaction", auto: true }, + ], + { + id: "msg_1001_assistant_aborted", + error: { name: "MessageAbortedError", data: { message: "Stopped" } }, + }, + ) + const failed = assistantMessage([{ id: "prt_after_abort", type: "text", text: "After interruption" }], { + id: "msg_1002_assistant_failed", + error: { + name: "APIError", + data: { + message: JSON.stringify({ error: { type: "provider_error", message: "Visible provider failure" } }), + isRetryable: false, + }, + }, + created: 1700000003000, + }) + const nextUser = userMessage([userText("Second turn", { id: "prt_second_user" })], { + id: "msg_2000_second_user", + created: 1700000005000, + }) + const nextAssistant = assistantMessage([{ id: "prt_second_text", type: "text", text: "Second response" }], { + id: "msg_2001_second_assistant", + parentID: "msg_2000_second_user", + created: 1700000006000, + }) + const timeline = await setupTimeline(page, { messages: [firstUser, aborted, failed, nextUser, nextAssistant] }) + await timeline.send(status("idle"), 100) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate((element) => (element.scrollTop = 0)) + + await expect(page.locator('[data-timeline-row="TurnDivider"]')).toHaveCount(1) + await expect(page.getByText("Session compacted", { exact: true })).toBeVisible() + await expect(page.getByText("Visible provider failure")).toBeVisible() + await scroller.evaluate((element) => (element.scrollTop = element.scrollHeight)) + await expect(page.locator('[data-timeline-row="TurnGap"]')).toBeVisible() + }) + + test("renders comment strips and historical diff summary overflow", async ({ page }) => { + const user = userMessage( + [ + userText("The user made the following comment regarding lines 4 through 8 of src/a.ts: Keep this stable", { + id: "prt_comment_only", + synthetic: true, + metadata: { + opencodeComment: { + path: "src/a.ts", + selection: { startLine: 4, startChar: 0, endLine: 8, endChar: 0 }, + comment: "Keep this stable", + }, + }, + }), + userText("Continue after the comment", { id: "prt_comment_visible" }), + ], + { summary: { diffs: Array.from({ length: 11 }, (_, index) => summaryDiff(index)) } }, + ) + const nextUser = userMessage(undefined, { id: "msg_2000_diff_next_user", created: 1700000010000 }) + const nextAssistant = assistantMessage([], { + id: "msg_2001_diff_next_assistant", + parentID: "msg_2000_diff_next_user", + created: 1700000011000, + }) + await setupTimeline(page, { messages: [user, assistantMessage(), nextUser, nextAssistant] }) + const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) + await scroller.evaluate((element) => (element.scrollTop = 0)) + + await expect(page.locator('[data-timeline-row="CommentStrip"]')).toBeVisible() + await expect(page.getByText("Keep this stable", { exact: true })).toBeVisible() + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toBeVisible() + await expect(page.getByText(/show all/i)).toBeVisible() + }) + + test("renders interruption independently when the turn is not compacted", async ({ page }) => { + const user = userMessage() + const before = assistantMessage([{ id: "prt_before", type: "text", text: "Before" }], { + id: "msg_1001_before", + error: { name: "MessageAbortedError", data: { message: "Stopped" } }, + }) + const after = assistantMessage([{ id: "prt_after", type: "text", text: "After" }], { + id: "msg_1002_after", + created: 1700000003000, + }) + await setupTimeline(page, { messages: [user, before, after] }) + + await expect(page.getByText("Interrupted", { exact: true })).toBeVisible() + const rows = await page + .locator('[data-timeline-row="AssistantPart"], [data-timeline-row="TurnDivider"]') + .evaluateAll((elements) => elements.map((element) => element.getAttribute("data-timeline-row"))) + expect(rows).toEqual(["AssistantPart", "TurnDivider", "AssistantPart"]) + }) + + test("renders user image, file attachment, file reference, and agent reference", async ({ page }) => { + const text = "Use @explore with @src/a.ts and inspect the attachments" + const parts: PartSeed<"user">[] = [ + userText(text, { id: "prt_user_rich" }), + { + id: "prt_user_image", + type: "file", + mime: "image/png", + filename: "pixel.png", + url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + }, + { + id: "prt_user_attachment", + type: "file", + mime: "application/json", + filename: "tsconfig.json", + url: "data:application/json;base64,e30=", + }, + { + id: "prt_user_reference", + type: "file", + mime: "text/plain", + filename: "a.ts", + url: "src/a.ts", + source: { type: "file", path: "src/a.ts", text: { value: "@src/a.ts", start: 18, end: 27 } }, + }, + { + id: "prt_user_agent", + type: "agent", + name: "explore", + source: { value: "@explore", start: 4, end: 12 }, + }, + ] + await setupTimeline(page, { messages: [userMessage(parts), assistantMessage()] }) + + await expect(page.getByAltText("pixel.png")).toBeVisible() + await expect(page.getByText("tsconfig.json")).toBeVisible() + await expect(page.getByText("@src/a.ts", { exact: true })).toBeVisible() + await expect(page.getByText("@explore", { exact: true })).toBeVisible() + }) +}) + +function editPart(id: string) { + return toolPart( + id, + "edit", + "completed", + { filePath: "src/a.ts" }, + { + metadata: { + filediff: { + file: "src/a.ts", + additions: 1, + deletions: 1, + before: "export const value = 1\n", + after: "export const value = 2\n", + }, + }, + }, + ) +} + +function patchPart(id: string) { + return toolPart( + id, + "patch", + "completed", + { files: ["src/a.ts", "src/b.ts"] }, + { + metadata: { + files: [ + patchFile("src/a.ts", "update"), + patchFile("src/b.ts", "add"), + patchFile("src/old.ts", "delete"), + { ...patchFile("src/moved.ts", "move"), move: "src/new-place.ts" }, + ], + }, + }, + ) +} + +function patchFile(filePath: string, type: "add" | "update" | "delete" | "move") { + return { + filePath, + relativePath: filePath, + type, + additions: type === "delete" ? 0 : 1, + deletions: type === "add" ? 0 : 1, + before: type === "add" ? undefined : "export const before = true\n", + after: type === "delete" ? undefined : "export const after = true\n", + } +} + +function summaryDiff(index: number) { + return { + file: `src/diff-${index}.ts`, + additions: 1, + deletions: 1, + patch: `@@ -1 +1 @@\n-export const value = ${index}\n+export const value = ${index + 1}`, + } +} diff --git a/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts b/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts new file mode 100644 index 000000000000..7c0864e5845b --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts @@ -0,0 +1,93 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + reasoningPart, + setupTimeline, + status, + textPart, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +const profiles = [ + { name: "summaries off no reasoning", summaries: false, reasoning: "", other: false, thinking: true, body: false }, + { + name: "summaries off reasoning heading", + summaries: false, + reasoning: "## Inspecting stability", + other: false, + thinking: true, + body: false, + }, + { + name: "summaries off with visible tool", + summaries: false, + reasoning: "## Inspecting stability", + other: true, + thinking: true, + body: false, + }, + { name: "summaries on no content", summaries: true, reasoning: "", other: false, thinking: true, body: false }, + { + name: "summaries on blank reasoning", + summaries: true, + reasoning: " ", + other: false, + thinking: true, + body: false, + }, + { + name: "summaries on visible reasoning", + summaries: true, + reasoning: "## Inspecting stability", + other: false, + thinking: false, + body: true, + }, + { + name: "summaries on visible tool no reasoning", + summaries: true, + reasoning: "", + other: true, + thinking: false, + body: false, + }, +] as const + +for (const profile of profiles) { + test(`projects busy reasoning profile ${profile.name}`, async ({ page }) => { + const reasoningID = `prt_reasoning_matrix_${profiles.indexOf(profile)}` + const parts = [ + ...(profile.reasoning ? [reasoningPart(reasoningID, profile.reasoning)] : []), + ...(profile.other + ? [toolPart(`prt_reasoning_tool_${profiles.indexOf(profile)}`, "skill", "running", { name: "inspect" })] + : []), + ] + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage(parts, { completed: false })], + settings: { showReasoningSummaries: profile.summaries }, + }) + await timeline.send(status("busy"), 150) + + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(profile.thinking ? 1 : 0) + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(profile.body ? 1 : 0) + if (!profile.summaries && profile.reasoning.trim()) { + await expect(page.getByText("Inspecting stability", { exact: true })).toBeVisible() + } + }) +} + +test("does not infer reasoning visibility from provider identity", async ({ page }) => { + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([textPart("prt_provider_text", "No reasoning payload")], { completed: false }), + ], + settings: { showReasoningSummaries: true }, + }) + await timeline.send(status("busy"), 150) + + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-part-id*="reasoning"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-part-id="prt_provider_text"]')).toBeVisible() +}) diff --git a/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts b/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts new file mode 100644 index 000000000000..ad35eef601cb --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts @@ -0,0 +1,43 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + completedAssistantInfo, + messageUpdated, + partUpdated, + setupTimeline, + shell, + status, + textPart, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("groups singleton and separated context operations at correct boundaries", async ({ page }) => { + const parts = [ + toolPart("prt_boundary_01_read", "read", "completed", { filePath: "src/a.ts" }), + textPart("prt_boundary_02_text", "Boundary text"), + toolPart("prt_boundary_03_glob", "glob", "completed", { path: ".", pattern: "**/*.ts" }), + toolPart("prt_boundary_04_grep", "grep", "completed", { path: ".", pattern: "stable" }), + shell("prt_boundary_05_shell", "completed", "done"), + toolPart("prt_boundary_06_list", "list", "completed", { path: "src" }), + ] + await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] }) + + await expect(page.locator('[data-timeline-part-ids="prt_boundary_01_read"]')).toBeVisible() + await expect(page.locator('[data-timeline-part-ids="prt_boundary_03_glob,prt_boundary_04_grep"]')).toBeVisible() + await expect(page.locator('[data-timeline-part-ids="prt_boundary_06_list"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="AssistantPart"]')).toHaveCount(5) +}) + +test("reducer-hardening: converges when idle arrives before final part and message completion", async ({ page }) => { + const textID = "prt_event_order_text" + const assistant = assistantMessage([textPart(textID, "Partial")], { completed: false }) + const timeline = await setupTimeline(page, { messages: [userMessage(), assistant] }) + await timeline.send(status("busy"), 100) + await timeline.send(status("idle"), 100) + await timeline.send(partUpdated(textPart(textID, "Final after early idle")), 120) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 250) + + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator(`[data-timeline-part-id="${textID}"]`)).toContainText("Final after early idle") +}) diff --git a/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts b/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts new file mode 100644 index 000000000000..54139cc3711b --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts @@ -0,0 +1,228 @@ +import { expect, test, type Locator, type Page } from "@playwright/test" +import { + assistantMessage, + setupTimeline, + shell, + textPart, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +for (const deviceScaleFactor of [1.25, 1.5]) { + test(`keeps the shell outline inside a fractionally short virtual row at ${deviceScaleFactor}x`, async ({ page }) => { + const shellID = "prt_shell_outline" + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(shellID, "completed", "shell output")])], + settings: { newLayoutDesigns: true, shellToolPartsExpanded: true }, + reducedMotion: true, + deviceScaleFactor, + }) + const part = page.locator(`[data-timeline-part-id="${shellID}"]`) + const output = part.locator('[data-component="bash-output"]') + const row = page.locator("[data-timeline-key]", { has: part }) + await expect(output).toBeVisible() + await timeline.settle() + + const geometry = await row.evaluate((element) => { + const output = element.querySelector('[data-component="bash-output"]') + if (!output) throw new Error("Shell output is unavailable") + const rowRect = element.getBoundingClientRect() + const outputRect = output.getBoundingClientRect() + // Match a rounded-down measurement at a fractional device-pixel phase. + element.style.height = `${outputRect.bottom - rowRect.top - 0.49}px` + element.style.transform = "translateY(0.25px)" + output.style.setProperty("--v2-border-border-base", "rgb(255, 0, 255)") + output.style.setProperty("background", "rgb(0, 0, 0)", "important") + const style = getComputedStyle(output) + return { + outputWidth: outputRect.width, + outputHeight: outputRect.height, + borderColor: style.borderTopColor, + boxShadow: style.boxShadow, + clipMargin: getComputedStyle(element).overflowClipMargin, + } + }) + await timeline.settle() + + const clipped = await row.evaluate((element) => { + const output = element.querySelector('[data-component="bash-output"]')! + return output.getBoundingClientRect().bottom - element.getBoundingClientRect().bottom + }) + expect(clipped).toBeCloseTo(0.49, 1) + + expect(await page.evaluate(() => devicePixelRatio)).toBe(deviceScaleFactor) + const edges = await captureCardEdges(page, output) + + expect(edges.box.width).toBeCloseTo(geometry.outputWidth, 2) + expect(edges.box.height).toBeCloseTo(geometry.outputHeight, 2) + expect(geometry.borderColor).toBe("rgb(255, 0, 255)") + expect(geometry.boxShadow).toBe("none") + expect(geometry.clipMargin).toBe("0.5px") + expect(edges.magenta.top).toBeGreaterThan(0.75) + expect(edges.magenta.bottom).toBeGreaterThan(0.75) + expect(edges.magenta.vertical).toBeGreaterThanOrEqual(2) + }) +} + +test("keeps the patch card inside a fractionally short virtual row", async ({ page }) => { + const patchID = "prt_patch_outline" + const file = { + filePath: "src/outline.ts", + relativePath: "src/outline.ts", + type: "update", + additions: 1, + deletions: 1, + before: "const outline = false\n", + after: "const outline = true\n", + } + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(patchID, "apply_patch", "completed", { files: [file.filePath] }, { metadata: { files: [file] } }), + ]), + ], + settings: { editToolPartsExpanded: true, newLayoutDesigns: true }, + reducedMotion: true, + }) + const part = page.locator(`[data-timeline-part-id="${patchID}"]`) + const card = part.locator('[data-component="accordion"][data-scope="apply-patch"]') + const row = page.locator("[data-timeline-key]", { has: part }) + await expect(card).toBeVisible() + await timeline.settle() + + const geometry = await row.evaluate((element) => { + const card = element.querySelector('[data-component="accordion"][data-scope="apply-patch"]') + if (!card) throw new Error("Patch card is unavailable") + const rowRect = element.getBoundingClientRect() + const cardRect = card.getBoundingClientRect() + element.style.height = `${cardRect.bottom - rowRect.top - 0.49}px` + const clipMargin = getComputedStyle(element).overflowClipMargin + const bottom = element.getBoundingClientRect().bottom + return { + overflow: card.getBoundingClientRect().bottom - bottom, + paintOverflow: card.getBoundingClientRect().bottom - bottom - Number.parseFloat(clipMargin), + clipMargin, + cardWidth: cardRect.width, + cardHeight: cardRect.height, + } + }) + await timeline.settle() + + expect(geometry.overflow).toBeCloseTo(0.49, 1) + expect(geometry.paintOverflow).toBeLessThanOrEqual(0) + const edges = await captureCardEdges(page, card) + expect(edges.box.width).toBeCloseTo(geometry.cardWidth, 2) + expect(edges.box.height).toBeCloseTo(geometry.cardHeight, 2) + expect(edges.luminance.top).toBeLessThan(245) + expect(edges.luminance.bottom).toBeLessThan(245) + expect(Math.abs(edges.luminance.bottom - edges.luminance.top)).toBeLessThan(10) + expect(geometry.clipMargin).toBe("0.5px") +}) + +test("allows paint rounding for every framed row but not fixed turn gaps", async ({ page }) => { + const secondUserID = "msg_outline_second_user" + await setupTimeline(page, { + messages: [ + userMessage(undefined, { + summary: { + diffs: [ + { + file: "src/summary.ts", + additions: 1, + deletions: 1, + patch: "@@ -1 +1 @@\n-export const value = 1\n+export const value = 2", + }, + ], + }, + }), + assistantMessage([textPart("prt_outline_text", "Assistant text")]), + userMessage(undefined, { id: secondUserID, created: 1700000010000 }), + assistantMessage([], { + id: "msg_outline_second_assistant", + parentID: secondUserID, + created: 1700000011000, + }), + ], + }) + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="TurnGap"]')).toBeVisible() + + const rows = await page.locator("[data-timeline-key]").evaluateAll((elements) => + elements.map((element) => ({ + tag: element.querySelector("[data-timeline-row]")?.dataset.timelineRow, + clipMargin: getComputedStyle(element).overflowClipMargin, + })), + ) + expect(rows.filter((row) => row.tag !== "TurnGap").every((row) => row.clipMargin === "0.5px")).toBe(true) + expect(rows.filter((row) => row.tag === "TurnGap")).toEqual([{ tag: "TurnGap", clipMargin: "0px" }]) +}) + +async function captureCardEdges(page: Page, card: Locator) { + const box = await card.boundingBox() + if (!box) throw new Error("Tool card bounds are unavailable") + const viewport = page.viewportSize() + if (!viewport) throw new Error("Viewport bounds are unavailable") + const screenshot = await page.screenshot() + return page.evaluate( + async ({ source, box, viewport }) => { + const image = new Image() + image.src = source + await image.decode() + const canvas = document.createElement("canvas") + canvas.width = image.naturalWidth + canvas.height = image.naturalHeight + const context = canvas.getContext("2d") + if (!context) throw new Error("2D canvas is unavailable") + context.drawImage(image, 0, 0) + const scale = { + x: image.naturalWidth / viewport.width, + y: image.naturalHeight / viewport.height, + } + const rows = (candidates: number[]) => { + const left = Math.floor((box.x + 8) * scale.x) + const width = Math.floor((box.width - 16) * scale.x) + return candidates.map((row) => { + const pixels = context.getImageData(left, row, width, 1).data + const indexes = Array.from({ length: width }, (_, index) => index * 4) + return { + luminance: + indexes + .map((index) => (pixels[index]! + pixels[index + 1]! + pixels[index + 2]!) / 3) + .reduce((sum, value) => sum + value, 0) / width, + magenta: + indexes.filter((index) => pixels[index]! > 200 && pixels[index + 1]! < 180 && pixels[index + 2]! > 200) + .length / width, + } + }) + } + const pixels = context.getImageData(0, 0, image.naturalWidth, image.naturalHeight).data + const columns = new Uint32Array(image.naturalWidth) + for (let index = 0; index < pixels.length; index += 4) { + if (pixels[index]! <= 200 || pixels[index + 1]! >= 180 || pixels[index + 2]! <= 200) continue + columns[(index / 4) % image.naturalWidth] = columns[(index / 4) % image.naturalWidth]! + 1 + } + const top = box.y * scale.y + const bottom = (box.y + box.height) * scale.y + const topRows = rows([Math.floor(top) - 1, Math.floor(top), Math.ceil(top)]) + const bottomRows = rows([Math.floor(bottom) - 2, Math.floor(bottom) - 1, Math.ceil(bottom) - 1]) + return { + box, + luminance: { + top: Math.min(...topRows.map((row) => row.luminance)), + bottom: rows([Math.ceil(bottom) - 1])[0]!.luminance, + }, + magenta: { + top: Math.max(...topRows.map((row) => row.magenta)), + bottom: Math.max(...bottomRows.map((row) => row.magenta)), + vertical: Array.from(columns).filter((count) => count > box.height * scale.y * 0.75).length, + }, + } + }, + { + source: `data:image/png;base64,${screenshot.toString("base64")}`, + viewport, + box, + }, + ) +} diff --git a/packages/app/e2e/regression/session-timeline-tool-projection.spec.ts b/packages/app/e2e/regression/session-timeline-tool-projection.spec.ts new file mode 100644 index 000000000000..4c2c1c4eadb8 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-tool-projection.spec.ts @@ -0,0 +1,97 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + partUpdated, + setupTimeline, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("renders every tool error outcome without leaking hidden tools", async ({ page }) => { + const ordinary = ["bash", "edit", "write", "patch", "webfetch", "websearch", "task", "skill", "mcp_probe"] + const parts = ordinary.map((tool, index) => + toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }), + ) + parts.push( + toolPart("prt_question_dismissed", "question", "error", questionInput(), { + error: "The user dismissed this question", + }), + toolPart("prt_question_error", "question", "error", questionInput(), { error: "Question transport failed" }), + ) + await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] }) + + await expect(page.locator('[data-kind="tool-error-card"]')).toHaveCount(ordinary.length + 1) + await expect(page.getByText(/dismissed/i)).toBeVisible() + for (let index = 0; index < ordinary.length; index++) { + await expect(page.locator(`[data-timeline-part-id="prt_error_${index}"]`)).toBeVisible() + } +}) + +test("transitions shell and question through running error outcomes", async ({ page }) => { + const shellID = "prt_transition_error_shell" + const questionID = "prt_transition_error_question" + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage( + [ + toolPart(shellID, "bash", "pending", { command: "exit 1" }), + toolPart(questionID, "question", "pending", questionInput()), + ], + { completed: false }, + ), + ], + }) + await timeline.waitForPart(shellID) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0) + await timeline.send(partUpdated(toolPart(shellID, "bash", "running", { command: "exit 1" })), 120) + await timeline.send(partUpdated(toolPart(questionID, "question", "running", questionInput())), 180) + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0) + await timeline.send( + partUpdated(toolPart(shellID, "bash", "error", { command: "exit 1" }, { error: "Command exited 1" })), + 180, + ) + await timeline.send( + partUpdated( + toolPart(questionID, "question", "error", questionInput(), { error: "The user dismissed this question" }), + ), + 250, + ) + + await expect(page.locator(`[data-timeline-part-id="${shellID}"] [data-kind="tool-error-card"]`)).toBeVisible() + await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toContainText(/dismissed/i) +}) + +test("labels all web search provider variants", async ({ page }) => { + const parts = [ + toolPart( + "prt_search_parallel", + "websearch", + "completed", + { query: "parallel" }, + { metadata: { provider: "parallel" } }, + ), + toolPart("prt_search_exa", "websearch", "completed", { query: "exa" }, { metadata: { provider: "exa" } }), + toolPart("prt_search_generic", "websearch", "completed", { query: "generic" }), + ] + await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] }) + + await expect(page.getByRole("button", { name: /Parallel Web Search/ })).toBeVisible() + await expect(page.getByRole("button", { name: /Exa Web Search/ })).toBeVisible() + await expect(page.getByRole("button", { name: /^Web Search/ })).toBeVisible() +}) + +function questionInput() { + return { questions: [{ header: "Stability", question: "Keep it stable?", options: [] }] } +} + +function errorInput(tool: string) { + if (tool === "bash") return { command: "exit 1" } + if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" } + if (tool === "patch") return { files: ["src/error.ts"] } + if (tool === "webfetch") return { url: "https://example.com" } + if (tool === "websearch") return { query: "failure" } + if (tool === "task") return { description: "Fail task", subagent_type: "explore" } + if (tool === "skill") return { name: "failure" } + return { target: "failure" } +} diff --git a/packages/app/e2e/regression/session-timeline-tool-state.spec.ts b/packages/app/e2e/regression/session-timeline-tool-state.spec.ts new file mode 100644 index 000000000000..63646f4454c6 --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-tool-state.spec.ts @@ -0,0 +1,77 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + partUpdated, + setupTimeline, + toolPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("updates expanded web search links without resetting expansion", async ({ page }) => { + const searchID = "prt_websearch_mutation" + const input = { query: "timeline stability" } + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([toolPart(searchID, "websearch", "completed", input, { output: "https://example.com/one" })]), + ], + }) + const wrapper = page.locator(`[data-timeline-part-id="${searchID}"]`) + const trigger = wrapper.locator('[data-slot="collapsible-trigger"]') + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await timeline.send( + partUpdated( + toolPart(searchID, "websearch", "completed", input, { + output: "https://example.com/one\nhttps://example.com/two", + }), + ), + 300, + ) + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await expect(wrapper.locator('a[href="https://example.com/two"]')).toBeVisible() +}) + +test("preserves an expanded tool error card across duplicate delivery", async ({ page }) => { + const toolID = "prt_duplicate_error" + const failed = toolPart(toolID, "bash", "error", { command: "exit 1" }, { error: "Command failed visibly" }) + const timeline = await setupTimeline(page, { messages: [userMessage(), assistantMessage([failed])] }) + const wrapper = page.locator(`[data-timeline-part-id="${toolID}"]`) + const trigger = wrapper.locator('[data-slot="collapsible-trigger"]') + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await timeline.send(partUpdated(failed), 150) + await timeline.send(partUpdated(failed), 250) + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await expect(wrapper).toContainText("Command failed visibly") +}) + +test("renders multiple question answers and preserves open state on answer updates", async ({ page }) => { + const questionID = "prt_multi_question" + const input = { + questions: [ + { header: "First", question: "First choice?", options: [] }, + { header: "Second", question: "Second choice?", options: [], multiple: true }, + ], + } + const timeline = await setupTimeline(page, { + messages: [ + userMessage(), + assistantMessage([ + toolPart(questionID, "question", "completed", input, { metadata: { answers: [["A"], ["B", "C"]] } }), + ]), + ], + }) + const wrapper = page.locator(`[data-timeline-part-id="${questionID}"]`) + const trigger = wrapper.locator('[data-slot="collapsible-trigger"]') + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await timeline.send( + partUpdated( + toolPart(questionID, "question", "completed", input, { metadata: { answers: [["Updated"], ["B", "C"]] } }), + ), + 300, + ) + await expect(trigger).toHaveAttribute("aria-expanded", "true") + await expect(wrapper).toContainText("Updated") + await expect(wrapper).toContainText("B, C") +}) diff --git a/packages/app/e2e/regression/session-timeline-transport.spec.ts b/packages/app/e2e/regression/session-timeline-transport.spec.ts new file mode 100644 index 000000000000..850e966d0b0f --- /dev/null +++ b/packages/app/e2e/regression/session-timeline-transport.spec.ts @@ -0,0 +1,116 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + partUpdated, + setupTimeline, + status, + textPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +test("keeps one connection open while delivering multiple events", async ({ page }) => { + const timeline = await setupTimeline(page) + + const first = await timeline.transport.send(partUpdated(textPart("prt_transport_first", "first event"))) + const second = await timeline.transport.send(partUpdated(textPart("prt_transport_second", "second event"))) + + await timeline.waitForPart("prt_transport_first") + await timeline.waitForPart("prt_transport_second") + expect(first.connectionID).toBe(second.connectionID) + expect(await timeline.transport.connections()).toHaveLength(1) + expect(await timeline.transport.acknowledgements()).toHaveLength(2) +}) + +test("delivers a burst from one stream chunk", async ({ page }) => { + const timeline = await setupTimeline(page) + const acknowledgements = await timeline.transport.burst([ + partUpdated(textPart("prt_transport_burst_a", "burst a")), + partUpdated(textPart("prt_transport_burst_b", "burst b")), + ]) + + await timeline.waitForPart("prt_transport_burst_a") + await timeline.waitForPart("prt_transport_burst_b") + expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1]) + expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(2) +}) + +test("parses split JSON and a split multibyte code point", async ({ page }) => { + const timeline = await setupTimeline(page) + const payload = partUpdated(textPart("prt_transport_split", "split snowman \u2603\u2603\u2603")) + const encoded = new TextEncoder().encode(`data: ${JSON.stringify(payload)}\n\n`) + const snowman = new TextEncoder().encode("\u2603")[0]! + const multibyte = encoded.indexOf(snowman) + + const acknowledgement = await timeline.transport.split(payload, [9, multibyte + 1, multibyte + 2]) + + await timeline.waitForPart("prt_transport_split") + await expect(page.locator('[data-timeline-part-id="prt_transport_split"]')).toContainText( + "split snowman \u2603\u2603\u2603", + ) + expect(acknowledgement.chunkCount).toBe(4) +}) + +test("delivers server heartbeat without mutating the timeline", async ({ page }) => { + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([textPart("prt_transport_steady", "steady")])], + }) + const before = await page.locator("[data-timeline-row]").allTextContents() + + await timeline.transport.heartbeat() + await timeline.settle() + + expect(await page.locator("[data-timeline-row]").allTextContents()).toEqual(before) + expect(await timeline.transport.connections()).toHaveLength(1) +}) + +test("reconnects after a clean close", async ({ page }) => { + const timeline = await setupTimeline(page, { eventRetry: 10 }) + const first = await timeline.transport.waitForConnection() + + await timeline.transport.close() + const second = await timeline.transport.waitForConnection({ after: first.id }) + await timeline.transport.send(partUpdated(textPart("prt_transport_close", "after close"))) + + await timeline.waitForPart("prt_transport_close") + expect(second.id).toBeGreaterThan(first.id) + expect((await timeline.transport.connections())[0]?.endedBy).toBe("close") +}) + +test("reconnects after a stream error", async ({ page }) => { + const timeline = await setupTimeline(page, { eventRetry: 10 }) + const first = await timeline.transport.waitForConnection() + + await timeline.transport.error("contract failure") + const second = await timeline.transport.waitForConnection({ after: first.id }) + await timeline.transport.send(status("busy")) + + await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2) + expect(second.id).toBeGreaterThan(first.id) + expect((await timeline.transport.connections())[0]?.endedBy).toBe("error") +}) + +test("records event IDs and reconnect Last-Event-ID headers", async ({ page }) => { + const timeline = await setupTimeline(page, { eventRetry: 10 }) + const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), { + id: "timeline-event-7", + }) + await timeline.waitForPart("prt_transport_id") + + await timeline.transport.error("retry with event id") + const connection = await timeline.transport.waitForConnection({ after: first.connectionID }) + + expect(first.eventID).toBe("timeline-event-7") + expect(connection.headers["last-event-id"]).toBe("timeline-event-7") +}) + +test("passes through non-event fetches", async ({ page }) => { + const timeline = await setupTimeline(page) + + const health = await page.evaluate(async () => { + const response = await fetch("/global/health") + return response.json() + }) + + expect(health).toEqual({ healthy: true }) + expect(await timeline.transport.connections()).toHaveLength(1) +}) diff --git a/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts b/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts deleted file mode 100644 index fd0b00f71cdd..000000000000 --- a/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" -import { expect, test, type Page } from "@playwright/test" -import { mockOpenCodeServer } from "../utils/mock-server" -import { expectSessionTitle } from "../utils/waits" - -const directory = "C:/OpenCode/TodoDockNavigation" -const projectID = "proj_todo_dock_navigation" -const sourceID = "ses_todo_dock_source" -const otherID = "ses_todo_dock_other" -const sourceTitle = "Todo dock animation" -const otherTitle = "Separate session" - -const activeTodos = [ - { id: "todo-1", content: "Receive todos in the active session", status: "completed", priority: "high" }, - { id: "todo-2", content: "Keep the dock visible across tabs", status: "completed", priority: "high" }, - { id: "todo-3", content: "Close after the final todo", status: "in_progress", priority: "high" }, -] - -type EventPayload = { - directory: string - payload: Record -} - -test.use({ viewport: { width: 1440, height: 900 }, reducedMotion: "no-preference" }) - -test("animates todo lifecycle without replaying it across session tabs", async ({ page }) => { - test.setTimeout(90_000) - const events: EventPayload[] = [] - const todos: Record = { [sourceID]: [], [otherID]: [] } - - await mockOpenCodeServer(page, { - directory, - project: { - id: projectID, - worktree: directory, - vcs: "git", - name: "todo-dock-navigation", - time: { created: 1700000000000, updated: 1700000000000 }, - sandboxes: [], - }, - provider: { - all: [ - { - id: "opencode", - name: "OpenCode", - models: { - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - limit: { context: 200_000 }, - }, - }, - }, - ], - connected: ["opencode"], - default: { providerID: "opencode", modelID: "claude-opus-4-6" }, - }, - sessions: [session(sourceID, sourceTitle, 1700000000000), session(otherID, otherTitle, 1700000001000)], - pageMessages: () => ({ items: [] }), - events: () => events.splice(0, 1), - eventRetry: 16, - todos: (sessionID) => todos[sessionID] ?? [], - }) - await configurePage(page) - - await page.goto(sessionHref(sourceID)) - await expectSessionTitle(page, sourceTitle) - const dock = page.locator('[data-component="session-todo-dock"]') - await expect(dock).toHaveCount(0) - - events.push(statusEvent(sourceID, "busy")) - await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() - - await page.waitForTimeout(700) - const opening = sampleDock(page, 1_000) - todos[sourceID] = activeTodos - events.push(todoEvent(sourceID, activeTodos)) - await expect(dock).toBeVisible() - await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1) - expect((await opening).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true) - - await switchSession(page, otherID, otherTitle) - await expect(dock).toHaveCount(0) - - const returningOpen = sampleDock(page, 700) - await switchSession(page, sourceID, sourceTitle) - const openSamples = (await returningOpen).filter((sample) => sample.present) - expect(openSamples.length).toBeGreaterThan(0) - expect(openSamples[0]!.opacity).toBeGreaterThan(0.98) - expect(openSamples[0]!.height).toBeGreaterThan(70) - await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1) - - const completedTodos = activeTodos.map((todo) => ({ ...todo, status: "completed" })) - const closing = sampleDock(page, 1_000) - todos[sourceID] = completedTodos - events.push(todoEvent(sourceID, completedTodos)) - await expect(dock).toHaveCount(0) - expect((await closing).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true) - todos[sourceID] = [] - events.push(todoEvent(sourceID, [])) - - await switchSession(page, otherID, otherTitle) - const returningEmpty = sampleDock(page, 700) - await switchSession(page, sourceID, sourceTitle) - await expect(dock).toHaveCount(0) - expect((await returningEmpty).every((sample) => !sample.present)).toBe(true) -}) - -function session(id: string, title: string, created: number) { - return { - id, - slug: id, - projectID, - directory, - title, - version: "dev", - time: { created, updated: created }, - } -} - -function statusEvent(sessionID: string, type: "busy" | "idle"): EventPayload { - return { - directory, - payload: { type: "session.status", properties: { sessionID, status: { type } } }, - } -} - -function todoEvent(sessionID: string, next: typeof activeTodos): EventPayload { - return { - directory, - payload: { type: "todo.updated", properties: { sessionID, todos: next } }, - } -} - -async function configurePage(page: Page) { - const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` - await page.addInitScript( - ({ directory, dirBase64, server, sessionIDs }) => { - localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) - localStorage.setItem( - "opencode.global.dat:server", - JSON.stringify({ - projects: { local: [{ worktree: directory, expanded: true }] }, - lastProject: { local: directory }, - }), - ) - localStorage.setItem( - "opencode.global.dat:tabs", - JSON.stringify(sessionIDs.map((sessionId) => ({ type: "session", server, dirBase64, sessionId }))), - ) - }, - { directory, dirBase64: base64Encode(directory), server, sessionIDs: [sourceID, otherID] }, - ) -} - -function sessionHref(sessionID: string) { - const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` - return `/server/${base64Encode(server)}/session/${sessionID}` -} - -async function switchSession(page: Page, sessionID: string, title: string) { - const href = sessionHref(sessionID) - const tab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`).first() - await expect(tab).toBeVisible() - await tab.click() - await expectSessionTitle(page, title) -} - -function sampleDock(page: Page, duration: number) { - return page.evaluate(async (duration) => { - const samples: { present: boolean; height: number; opacity: number }[] = [] - const start = performance.now() - while (performance.now() - start < duration) { - const dock = document.querySelector('[data-component="session-todo-dock"]') - const clip = dock?.parentElement?.parentElement - const label = dock?.querySelector('[data-action="session-todo-toggle"] span[aria-label]') - samples.push({ - present: !!dock, - height: clip?.getBoundingClientRect().height ?? 0, - opacity: label ? Number.parseFloat(getComputedStyle(label).opacity) : 0, - }) - await new Promise(requestAnimationFrame) - } - return samples - }, duration) -} diff --git a/packages/app/e2e/regression/subagent-child-navigation.spec.ts b/packages/app/e2e/regression/subagent-child-navigation.spec.ts new file mode 100644 index 000000000000..19d2c29af025 --- /dev/null +++ b/packages/app/e2e/regression/subagent-child-navigation.spec.ts @@ -0,0 +1,200 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/SubagentNavigation" +const projectID = "proj_subagent_navigation" +const parentID = "ses_subagent_parent" +const childID = "ses_subagent_child" +const parentTitle = "Parent session" +const childTitle = "Subagent child session" +// Child session pages derive their heading from the task part that spawned them. +const taskDescription = "Inspect child navigation" + +type EventPayload = { directory: string; payload: Record } + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("navigates to a subagent child session missing from the session list", async ({ page }) => { + await setup(page) + await openChildFromParent(page) + + await expectSessionTitle(page, taskDescription) + await expect(page.getByRole("heading", { name: parentTitle })).toHaveCount(0) + + const titlebarRight = page.locator("#opencode-titlebar-right") + await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1) +}) + +test("shows the not found fallback when the viewed session is deleted", async ({ page }) => { + const events: EventPayload[] = [] + await setup(page, () => events.splice(0, 1)) + await openChildFromParent(page) + await expectSessionTitle(page, taskDescription) + + events.push({ + directory, + payload: { type: "session.deleted", properties: { info: childSession() } }, + }) + + await expect(page.getByText("This session cannot be found")).toBeVisible() + await expect(page.getByRole("button", { name: "Close Tab" })).toBeVisible() + await expect(page.getByRole("heading", { name: taskDescription })).toHaveCount(0) +}) + +async function setup(page: Page, events?: () => EventPayload[]) { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "subagent-navigation", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { + "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6", limit: { context: 200_000 } }, + }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "claude-opus-4-6" }, + }, + sessions: [session(parentID, parentTitle, 1700000000000), childSession()], + pageMessages: (sessionID) => ({ items: sessionID === parentID ? parentMessages() : [] }), + events, + eventRetry: events ? 16 : undefined, + }) + // The child session resolves via /session/:id but is absent from the /session list, + // matching a subagent session that has not been loaded into the list cache yet. + await page.route( + (url) => url.pathname === "/session" && url.port === (process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"), + (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify([session(parentID, parentTitle, 1700000000000)]), + }), + ) + await configurePage(page) +} + +async function openChildFromParent(page: Page) { + await page.goto(sessionHref(parentID)) + await expectSessionTitle(page, parentTitle) + + const card = page.locator(`a[href="${sessionHref(childID)}"]`) + await expect(card).toBeVisible() + await card.click() + + await expect(page).toHaveURL(new RegExp(`/server/.+/session/${childID}$`), { timeout: 15_000 }) +} + +function session(id: string, title: string, created: number, extra?: Record) { + return { + id, + slug: id, + projectID, + directory, + title, + version: "dev", + time: { created, updated: created }, + ...extra, + } +} + +function childSession() { + return session(childID, childTitle, 1700000001000, { parentID }) +} + +function parentMessages() { + const userID = "msg_user_0001" + const assistantID = "msg_assistant_0001" + return [ + { + info: { + id: userID, + sessionID: parentID, + role: "user", + time: { created: 1700000000000 }, + agent: "build", + model: { providerID: "opencode", modelID: "claude-opus-4-6" }, + }, + parts: [ + { + id: "prt_user_text_0001", + sessionID: parentID, + messageID: userID, + type: "text", + text: "Delegate work to a subagent", + }, + ], + }, + { + info: { + id: assistantID, + sessionID: parentID, + role: "assistant", + time: { created: 1700000001000, completed: 1700000002000 }, + parentID: userID, + modelID: "claude-opus-4-6", + providerID: "opencode", + mode: "build", + agent: "build", + path: { cwd: directory, root: directory }, + cost: 0.01, + tokens: { input: 100, output: 200, reasoning: 0, cache: { read: 0, write: 0 } }, + finish: "stop", + }, + parts: [ + { + id: "prt_tool_task_0001", + sessionID: parentID, + messageID: assistantID, + type: "tool", + callID: "call_task_0001", + tool: "task", + state: { + status: "completed", + input: { description: taskDescription, subagent_type: "explore" }, + output: "Subagent finished", + title: taskDescription, + metadata: { sessionId: childID }, + time: { start: 1700000001000, end: 1700000002000 }, + }, + }, + ], + }, + ] +} + +async function configurePage(page: Page) { + const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + await page.addInitScript( + ({ directory, server, sessionId }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem("opencode.window.browser.dat:tabs", JSON.stringify([{ type: "session", server, sessionId }])) + }, + { directory, server, sessionId: parentID }, + ) +} + +function sessionHref(sessionID: string) { + const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` + return `/server/${base64Encode(server)}/session/${sessionID}` +} diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts new file mode 100644 index 000000000000..6bc417af8085 --- /dev/null +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -0,0 +1,108 @@ +import { expect, test, type Page, type Route } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" + +const server = "http://127.0.0.1:4096" +const sessionA = session("ses_tab_a", "Tab A session") +const sessionB = session("ses_tab_b", "Tab B session") + +test("pressing mouse down on a tab navigates before mouse up", async ({ page }) => { + await mockServer(page) + await page.addInitScript( + ({ server, sessionA, sessionB }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify([ + { type: "session", server, sessionId: sessionA }, + { type: "session", server, sessionId: sessionB }, + ]), + ) + }, + { server, sessionA: sessionA.id, sessionB: sessionB.id }, + ) + + const hrefA = `/server/${base64Encode(server)}/session/${sessionA.id}` + const hrefB = `/server/${base64Encode(server)}/session/${sessionB.id}` + await page.goto(hrefA) + await expect(page.getByText(sessionA.title).first()).toBeVisible() + + const linkB = page.locator(`a[data-titlebar-tab-link][href="${hrefB}"]`) + await expect(linkB).toBeVisible() + const box = await linkB.boundingBox() + if (!box) throw new Error("tab link has no bounding box") + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2) + await page.mouse.down() + + // Navigation must happen on mousedown, before the button is released. + await expect(page).toHaveURL(new RegExp(`${hrefB.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`)) + await page.mouse.up() + await expect(page).toHaveURL(new RegExp(`${hrefB.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`)) +}) + +function session(id: string, title: string) { + return { + id, + slug: id, + projectID: "project-tabs", + directory: "C:/tab-project", + title, + version: "dev", + time: { created: 1, updated: 1 }, + } +} + +async function mockServer(page: Page) { + const sessions = [sessionA, sessionB] + await page.route("**/*", async (route) => { + const url = new URL(route.request().url()) + if (url.origin !== server) return route.fallback() + if (url.pathname === "/global/event" || url.pathname === "/event") return sse(route) + if (url.pathname === "/global/health") return json(route, { healthy: true }) + if (url.pathname === "/session") return json(route, sessions) + const byId = sessions.find((item) => url.pathname === `/session/${item.id}`) + if (byId) return json(route, byId) + if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) + if (/^\/session\/[^/]+\/message$/.test(url.pathname)) return json(route, []) + if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, []) + if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) + return json(route, []) + if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname)) + return json(route, {}) + if (url.pathname === "/provider") + return json(route, { all: [], connected: [], default: { providerID: "", modelID: "" } }) + if (url.pathname === "/agent") return json(route, [{ name: "build", mode: "primary" }]) + if (url.pathname === "/project" || url.pathname === "/project/current") { + const project = { + id: sessionA.projectID, + worktree: sessionA.directory, + vcs: "git", + time: { created: 1, updated: 1 }, + sandboxes: [], + } + return json(route, url.pathname === "/project" ? [project] : project) + } + if (url.pathname === "/path") + return json(route, { + state: sessionA.directory, + config: sessionA.directory, + worktree: sessionA.directory, + directory: sessionA.directory, + home: sessionA.directory, + }) + if (url.pathname === "/vcs") return json(route, { branch: "main", default_branch: "main" }) + return json(route, {}) + }) +} + +function json(route: Route, body: unknown, status = 200) { + return route.fulfill({ + status, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify(body), + }) +} + +function sse(route: Route) { + return route.fulfill({ status: 200, contentType: "text/event-stream", body: ": ok\n\n" }) +} diff --git a/packages/app/e2e/regression/terminal-composer-focus.spec.ts b/packages/app/e2e/regression/terminal-composer-focus.spec.ts new file mode 100644 index 000000000000..2c2801d4b5bf --- /dev/null +++ b/packages/app/e2e/regression/terminal-composer-focus.spec.ts @@ -0,0 +1,89 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/TerminalComposerFocus" +const projectID = "proj_terminal_composer_focus" +const sessionID = "ses_terminal_composer_focus" +const ptyID = "pty_terminal_composer_focus" + +test.use({ viewport: { width: 1440, height: 900 } }) + +test("routes typing to the composer unless the open terminal is focused", async ({ page }) => { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "terminal-composer-focus", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [ + { + id: sessionID, + slug: "terminal-composer-focus", + projectID, + directory, + title: "Terminal composer focus", + version: "dev", + time: { created: 1700000000000, updated: 1700000000000 }, + }, + ], + pageMessages: () => ({ items: [] }), + }) + await page.route("**/pty", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ id: ptyID, title: "Terminal 1" }), + }), + ) + await page.route(`**/pty/${ptyID}`, (route) => + route.fulfill({ status: 200, contentType: "application/json", body: "{}" }), + ) + await page.route(`**/pty/${ptyID}/connect-token*`, (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify({ ticket: "e2e-ticket" }), + }), + ) + await page.routeWebSocket(new RegExp(`/pty/${ptyID}/connect`), () => undefined) + await page.addInitScript(() => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + }) + + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, "Terminal composer focus") + + const composer = page.locator('[data-component="prompt-input"]') + const terminal = page.locator('[data-component="terminal"]') + await page.keyboard.press("Control+Backquote") + await expect(terminal).toBeVisible() + await expect.poll(() => terminal.evaluate((element) => element.contains(document.activeElement))).toBe(true) + + await page.keyboard.type("x") + await expect(composer).toHaveText("") + + await page.waitForTimeout(300) + await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur()) + await page.keyboard.type("a") + + await expect(composer).toBeFocused() + await expect(composer).toHaveText("a") +}) diff --git a/packages/app/e2e/regression/terminal-hidden.spec.ts b/packages/app/e2e/regression/terminal-hidden.spec.ts index 357d9ac203e3..73821580af07 100644 --- a/packages/app/e2e/regression/terminal-hidden.spec.ts +++ b/packages/app/e2e/regression/terminal-hidden.spec.ts @@ -7,7 +7,7 @@ const projectID = "proj_hidden_terminal_regression" const sessionID = "ses_hidden_terminal_regression" const title = "Hidden terminal regression" -test("unmounts the terminal renderer while the pane is hidden", async ({ page }) => { +test("unmounts the terminal panel while it is hidden", async ({ page }) => { await page.setViewportSize({ width: 1400, height: 900 }) await mockOpenCodeServer(page, { directory, @@ -64,13 +64,14 @@ test("unmounts the terminal renderer while the pane is hidden", async ({ page }) await expect(page.locator('[data-component="terminal"]')).toBeVisible() await page.keyboard.press("Control+Backquote") - await expect(panel).toHaveAttribute("aria-hidden", "true") + await expect(panel).toHaveCount(0) await expect(page.locator('[data-component="terminal"]')).toHaveCount(0) await page.setViewportSize({ width: 1200, height: 700 }) await expect(page.locator('[data-component="terminal"]')).toHaveCount(0) await page.keyboard.press("Control+Backquote") + await expect(panel).toBeVisible() await expect(page.locator('[data-component="terminal"]')).toBeVisible() }) diff --git a/packages/app/e2e/regression/terminal-tab-switch.spec.ts b/packages/app/e2e/regression/terminal-tab-switch.spec.ts new file mode 100644 index 000000000000..cbb72958ad3a --- /dev/null +++ b/packages/app/e2e/regression/terminal-tab-switch.spec.ts @@ -0,0 +1,145 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test, type Page } from "@playwright/test" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/TerminalTabSwitch" +const projectID = "proj_terminal_tab_switch" +const sessionA = "ses_terminal_tab_a" +const sessionB = "ses_terminal_tab_b" +const titleA = "Alpha session" +const titleB = "Beta session" +const ptyID = "pty_tab_switch" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` +// Marks the terminal DOM node so a remount (fresh node) is detectable. +const PROBE = "original" + +test.use({ viewport: { width: 1440, height: 900 } }) + +// Terminals are workspace-scoped: switching between session tabs in the same +// workspace must keep the terminal mounted and its PTY connection open instead +// of tearing it down and reconnecting. +test("keeps the terminal session alive when switching session tabs in a workspace", async ({ page }) => { + const connections = await setup(page) + + await page.goto(sessionHref(sessionA)) + await expectSessionTitle(page, titleA) + + await page.keyboard.press("Control+Backquote") + const terminal = page.locator('[data-component="terminal"]') + await expect(terminal).toBeVisible() + await expect.poll(() => connections.length).toBe(1) + await writeProbe(page) + + await switchTab(page, titleB) + await expectSessionTitle(page, titleB) + await expect(terminal).toBeVisible() + expect(await readProbe(page)).toBe(PROBE) + expect(connections.length).toBe(1) + + await switchTab(page, titleA) + await expectSessionTitle(page, titleA) + await expect(terminal).toBeVisible() + expect(await readProbe(page)).toBe(PROBE) + expect(connections.length).toBe(1) +}) + +type Probed = HTMLElement & { __e2eProbe?: string } + +async function switchTab(page: Page, title: string) { + await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click() +} + +async function writeProbe(page: Page) { + await page.locator('[data-component="terminal"]').evaluate((el, probe) => { + ;(el as Probed).__e2eProbe = probe + }, PROBE) +} + +async function readProbe(page: Page) { + return page.locator('[data-component="terminal"]').evaluate((el) => (el as Probed).__e2eProbe) +} + +async function setup(page: Page) { + await mockOpenCodeServer(page, { + directory, + project: { + id: projectID, + worktree: directory, + vcs: "git", + name: "terminal-tab-switch", + time: { created: 1700000000000, updated: 1700000000000 }, + sandboxes: [], + }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } }, + }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)], + pageMessages: () => ({ items: [] }), + }) + await page.route("**/pty", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ id: ptyID, title: "Terminal 1" }), + }), + ) + await page.route(`**/pty/${ptyID}`, (route) => + route.fulfill({ status: 200, contentType: "application/json", body: "{}" }), + ) + await page.route(`**/pty/${ptyID}/connect-token*`, (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify({ ticket: "e2e-ticket" }), + }), + ) + const connections: string[] = [] + await page.routeWebSocket(new RegExp(`/pty/${ptyID}/connect`), (ws) => { + connections.push(ws.url()) + }) + + await page.addInitScript( + ({ directory, server, sessions }) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + localStorage.setItem( + "opencode.window.browser.dat:tabs", + JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))), + ) + }, + { directory, server, sessions: [sessionA, sessionB] }, + ) + return connections +} + +function session(id: string, title: string, created: number) { + return { + id, + slug: id, + projectID, + directory, + title, + version: "dev", + time: { created, updated: created }, + } +} + +function sessionHref(sessionID: string) { + return `/server/${base64Encode(server)}/session/${sessionID}` +} diff --git a/packages/app/e2e/smoke/session-timeline.fixture.ts b/packages/app/e2e/smoke/session-timeline.fixture.ts index 3dce37cafd9d..beb2d7cf75ee 100644 --- a/packages/app/e2e/smoke/session-timeline.fixture.ts +++ b/packages/app/e2e/smoke/session-timeline.fixture.ts @@ -120,7 +120,7 @@ function toolPart( outputLength = 160, ): MessagePart { const metadata = - tool === "apply_patch" + tool === "patch" ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } : tool === "edit" || tool === "write" ? { @@ -199,7 +199,7 @@ function turn(index: number): Message[] { ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] : []), ...(index % 8 === 0 - ? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] + ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] : []), ...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []), ...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []), @@ -229,7 +229,6 @@ const sourceMessages = Array.from({ length: 12 }, (_, index) => [ ]).flat() function renderable(part: MessagePart) { - if (part.type === "tool" && part.tool === "todowrite") return false if (part.type === "text") return !!part.text.trim() if (part.type === "reasoning") return !!part.text.trim() return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch" diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index 5bca533e6a60..a73cc0ccdd7d 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -127,7 +127,7 @@ test.describe("smoke: session timeline", () => { await page.addInitScript( ({ dirBase64, sourceID, targetID }) => { localStorage.setItem( - "opencode.global.dat:tabs", + "opencode.window.browser.dat:tabs", JSON.stringify( [sourceID, targetID].map((sessionId) => ({ type: "session", @@ -253,7 +253,7 @@ test.describe("smoke: session timeline", () => { await page.addInitScript( ({ dirBase64, sourceID, targetID }) => { localStorage.setItem( - "opencode.global.dat:tabs", + "opencode.window.browser.dat:tabs", JSON.stringify( [sourceID, targetID].map((sessionId) => ({ type: "session", diff --git a/packages/app/e2e/tsconfig.json b/packages/app/e2e/tsconfig.json index 3f1cad80cb60..53aacbda02e9 100644 --- a/packages/app/e2e/tsconfig.json +++ b/packages/app/e2e/tsconfig.json @@ -5,5 +5,13 @@ "rootDir": "..", "types": ["node", "bun"] }, - "include": ["./**/*.ts"] + "include": [ + "./performance/timeline-stability/**/*.spec.ts", + "./performance/timeline-stability/fixture.test.ts", + "./performance/timeline-stability/fixture.ts", + "./performance/unit/visual-stability.test.ts", + "./regression/new-session-panel-corner.spec.ts", + "./regression/session-timeline-context-resize.spec.ts", + "./utils/**/*.ts" + ] } diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index 875c3b7a96c3..34c60ba7f4d8 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -1,7 +1,7 @@ import type { Page, Route } from "@playwright/test" const emptyList = new Set(["/skill", "/command", "/lsp", "/formatter", "/vcs/status", "/vcs/diff"]) -const emptyObject = new Set(["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"]) +const emptyObject = new Set(["/global/config", "/config", "/provider/auth", "/mcp", "/experimental/resource"]) export interface MockServerConfig { provider: unknown @@ -11,12 +11,18 @@ export interface MockServerConfig { pageMessages: (sessionId: string, limit: number, before?: string) => { items: unknown[]; cursor?: string } vcsDiff?: unknown[] messageDelay?: number + beforeMessagesResponse?: (input: { sessionID: string; before?: string }) => Promise onMessages?: (input: { sessionID: string; before?: string; phase: "start" | "end" }) => void + message?: (sessionID: string, messageID: string) => unknown + onMessage?: (input: { sessionID: string; messageID: string }) => void events?: () => unknown[] eventRetry?: number - todos?: (sessionID: string) => unknown[] permissions?: unknown[] | (() => unknown[]) questions?: unknown[] | (() => unknown[]) + fileList?: (path: string) => unknown | Promise + fileContent?: (path: string) => unknown | Promise + findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown + sessionStatus?: unknown } export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { @@ -49,11 +55,34 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { const path = url.pathname if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry) if (path === "/global/health") return json(route, { healthy: true }) + if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true }) if (path === "/permission") return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? [])) if (path === "/question") return json(route, typeof config.questions === "function" ? config.questions() : (config.questions ?? [])) + if (path === "/session/status") return json(route, config.sessionStatus ?? {}) if (path === "/vcs/diff" && config.vcsDiff) return json(route, config.vcsDiff) + if (path === "/file" && config.fileList) + return json(route, await config.fileList(url.searchParams.get("path") ?? "")) + if (path === "/file/content" && config.fileContent) + return json(route, await config.fileContent(url.searchParams.get("path") ?? "")) + if (path === "/find/file" && config.findFiles) + return json( + route, + await config.findFiles({ + query: url.searchParams.get("query") ?? "", + dirs: url.searchParams.get("dirs") ?? undefined, + limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined, + }), + ) + if (path === "/api/reference") + return json(route, { + location: { + directory: config.directory, + project: { id: (config.project as { id?: string }).id, directory: config.directory }, + }, + data: [], + }) if (emptyObject.has(path)) return json(route, {}) if (emptyList.has(path)) return json(route, []) if (path in staticRoutes) return json(route, staticRoutes[path]) @@ -64,8 +93,18 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { return json(route, session ?? {}) } - const todoMatch = path.match(/^\/session\/([^/]+)\/todo$/) - if (todoMatch) return json(route, config.todos?.(todoMatch[1]!) ?? []) + const projectMatch = path.match(/^\/project\/([^/]+)$/) + if (projectMatch) return json(route, config.project) + + const messageMatch = path.match(/^\/session\/([^/]+)\/message\/([^/]+)$/) + if (messageMatch) { + config.onMessage?.({ sessionID: messageMatch[1]!, messageID: messageMatch[2]! }) + if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) + const message = config.message?.(messageMatch[1]!, messageMatch[2]!) + if (message === undefined) return json(route, { error: "Message not found" }, undefined, 404) + return json(route, message) + } + if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, []) const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/) @@ -74,7 +113,8 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { const before = token ? cursors.get(token) : undefined if (token && !before) return json(route, { error: "Invalid cursor" }, undefined, 400) config.onMessages?.({ sessionID: messagesMatch[1], before, phase: "start" }) - if (config.messageDelay) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) + await config.beforeMessagesResponse?.({ sessionID: messagesMatch[1]!, before }) + if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) const limit = Number(url.searchParams.get("limit") ?? 80) const pageData = config.pageMessages(messagesMatch[1], limit, before) config.onMessages?.({ sessionID: messagesMatch[1], before, phase: "end" }) diff --git a/packages/app/e2e/utils/sse-transport.ts b/packages/app/e2e/utils/sse-transport.ts new file mode 100644 index 000000000000..186962998d1b --- /dev/null +++ b/packages/app/e2e/utils/sse-transport.ts @@ -0,0 +1,284 @@ +import type { Page } from "@playwright/test" + +export type SseConnectionRecord = { + id: number + url: string + path: "/global/event" | "/event" + headers: Record + openedAt: number + endedAt?: number + endedBy?: "close" | "disconnect" | "error" | "abort" + error?: string +} + +export type SseDeliveryAcknowledgement = { + deliveryID: number + connectionID: number + bytes: number + chunkCount: number + deliveredAt: number + eventID?: string +} + +export type SseEventOptions = { + id?: string + event?: string + retry?: number + marker?: string +} + +export type SseTransport = { + server: string + waitForConnection(options?: { after?: number; timeout?: number }): Promise + send(payload: T, options?: SseEventOptions): Promise + burst(payloads: readonly T[], options?: readonly SseEventOptions[]): Promise + split(payload: T, cuts: readonly number[], options?: SseEventOptions): Promise + heartbeat(options?: SseEventOptions): Promise + writeRaw(value: string | Uint8Array, cuts?: readonly number[], marker?: string): Promise + close(): Promise + disconnect(message?: string): Promise + error(message?: string): Promise + connections(): Promise + acknowledgements(): Promise +} + +type BrowserCommand = + | { type: "send"; deliveries: { payload: T; options?: SseEventOptions }[]; burst: boolean; cuts?: number[] } + | { type: "raw"; bytes: number[]; cuts?: number[]; marker?: string } + | { type: "end"; mode: "close" | "disconnect" | "error"; message?: string } + | { type: "connections" } + | { type: "acknowledgements" } + +type BrowserTransport = Window & { + __testSseTransport?: { + command: (command: BrowserCommand) => unknown + } +} + +export async function installSseTransport( + page: Page, + options: { server: string; retry?: number }, +): Promise> { + const server = new URL(options.server).origin + await page.addInitScript( + ({ server, retry }) => { + type Connection = SseConnectionRecord & { controller: ReadableStreamDefaultController } + type ProbeWindow = Window & { + __visualStabilityProbe?: { startedAt: number; markers: { at: number; label: string }[] } + } + const originalFetch = window.fetch.bind(window) + const connections: Connection[] = [] + const acknowledgements: SseDeliveryAcknowledgement[] = [] + const encoder = new TextEncoder() + let nextConnectionID = 0 + let nextDeliveryID = 0 + + const current = () => connections.findLast((connection) => connection.endedAt === undefined) + const chunks = (bytes: Uint8Array, cuts?: readonly number[]) => { + const boundaries = [...new Set(cuts ?? [])] + .filter((cut) => Number.isInteger(cut) && cut > 0 && cut < bytes.byteLength) + .sort((a, b) => a - b) + return [0, ...boundaries].map((start, index) => bytes.slice(start, boundaries[index] ?? bytes.byteLength)) + } + const marker = (label?: string) => { + if (!label) return + const probe = (window as ProbeWindow).__visualStabilityProbe + if (!probe) return + probe.markers.push({ at: performance.now() - probe.startedAt, label }) + } + const frame = (payload: unknown, eventOptions: SseEventOptions = {}) => + [ + eventOptions.event === undefined ? "" : `event: ${eventOptions.event}\n`, + eventOptions.id === undefined ? "" : `id: ${eventOptions.id}\n`, + eventOptions.retry === undefined ? "" : `retry: ${eventOptions.retry}\n`, + `data: ${JSON.stringify(payload)}\n\n`, + ].join("") + const acknowledge = ( + connection: Connection, + bytes: number, + chunkCount: number, + eventID?: string, + ): SseDeliveryAcknowledgement => { + const acknowledgement = { + deliveryID: ++nextDeliveryID, + connectionID: connection.id, + bytes, + chunkCount, + deliveredAt: performance.now(), + ...(eventID === undefined ? {} : { eventID }), + } + acknowledgements.push(acknowledgement) + return acknowledgement + } + const end = (mode: "close" | "disconnect" | "error", message?: string) => { + const connection = current() + if (!connection) throw new Error("SSE transport has no active connection") + connection.endedAt = performance.now() + connection.endedBy = mode + if (message) connection.error = message + if (mode === "close") { + connection.controller.close() + return + } + const error = new DOMException( + message ?? "SSE connection disconnected", + mode === "error" ? "Error" : "NetworkError", + ) + connection.controller.error(error) + } + + const command = (input: BrowserCommand) => { + if (input.type === "connections") + return connections.map(({ controller: _controller, ...connection }) => connection) + if (input.type === "acknowledgements") return acknowledgements + if (input.type === "end") return end(input.mode, input.message) + const connection = current() + if (!connection) throw new Error("SSE transport has no active connection") + if (input.type === "raw") { + marker(input.marker) + const output = chunks(new Uint8Array(input.bytes), input.cuts) + output.forEach((chunk) => connection.controller.enqueue(chunk)) + return acknowledge(connection, input.bytes.length, output.length) + } + const encoded = input.deliveries.map((delivery) => ({ + delivery, + bytes: encoder.encode(frame(delivery.payload, delivery.options)), + })) + encoded.forEach((item) => marker(item.delivery.options?.marker)) + if (input.burst) { + const bytes = encoder.encode( + encoded.map((item) => frame(item.delivery.payload, item.delivery.options)).join(""), + ) + connection.controller.enqueue(bytes) + return encoded.map((item) => acknowledge(connection, item.bytes.byteLength, 1, item.delivery.options?.id)) + } + const output = chunks(encoded[0]!.bytes, input.cuts) + output.forEach((chunk) => connection.controller.enqueue(chunk)) + return acknowledge(connection, encoded[0]!.bytes.byteLength, output.length, encoded[0]!.delivery.options?.id) + } + + ;(window as BrowserTransport).__testSseTransport = { command } + const fetch = (input: RequestInfo | URL, init?: RequestInit) => { + const request = new Request(input, init) + const url = new URL(request.url) + if (url.origin !== server || (url.pathname !== "/global/event" && url.pathname !== "/event")) + return originalFetch(input, init) + + const id = ++nextConnectionID + const record = { + id, + url: url.href, + path: url.pathname, + headers: Object.fromEntries(request.headers.entries()), + openedAt: performance.now(), + } as Connection + const stream = new ReadableStream({ + start(controller) { + record.controller = controller + connections.push(record) + if (retry !== undefined) controller.enqueue(encoder.encode(`retry: ${retry}\n\n`)) + request.signal.addEventListener( + "abort", + () => { + if (record.endedAt !== undefined) return + record.endedAt = performance.now() + record.endedBy = "abort" + controller.error(request.signal.reason ?? new DOMException("The operation was aborted", "AbortError")) + }, + { once: true }, + ) + }, + cancel() { + if (record.endedAt !== undefined) return + record.endedAt = performance.now() + record.endedBy = "disconnect" + }, + }) + return Promise.resolve( + new Response(stream, { + status: 200, + headers: { + "cache-control": "no-cache", + "content-type": "text/event-stream", + }, + }), + ) + } + Object.defineProperty(window, "fetch", { configurable: true, writable: true, value: fetch }) + }, + { server, retry: options.retry }, + ) + + const command = (input: BrowserCommand) => + page.evaluate((input) => { + const transport = (window as BrowserTransport).__testSseTransport + if (!transport) throw new Error("SSE transport was not installed before page load") + return transport.command(input as BrowserCommand) + }, input) as Promise + + return { + server, + async waitForConnection(input = {}) { + await page.waitForFunction( + (after) => { + const transport = (window as BrowserTransport).__testSseTransport + const connections = transport?.command({ type: "connections" }) as SseConnectionRecord[] | undefined + return connections?.some((connection) => connection.id > after) + }, + input.after ?? 0, + { timeout: input.timeout }, + ) + return (await command({ type: "connections" })).findLast( + (connection) => connection.id > (input.after ?? 0), + )! + }, + send(payload, eventOptions) { + return command({ type: "send", deliveries: [{ payload, options: eventOptions }], burst: false }) + }, + burst(payloads, eventOptions = []) { + return command({ + type: "send", + deliveries: payloads.map((payload, index) => ({ payload, options: eventOptions[index] })), + burst: true, + }) + }, + split(payload, cuts, eventOptions) { + return command({ type: "send", deliveries: [{ payload, options: eventOptions }], burst: false, cuts: [...cuts] }) + }, + heartbeat(eventOptions) { + return command({ + type: "send", + deliveries: [ + { + payload: { directory: "global", payload: { type: "server.heartbeat", properties: {} } } as T, + options: eventOptions, + }, + ], + burst: false, + }) + }, + writeRaw(value, cuts, marker) { + return command({ + type: "raw", + bytes: Array.from(typeof value === "string" ? new TextEncoder().encode(value) : value), + cuts: cuts ? [...cuts] : undefined, + marker, + }) + }, + close() { + return command({ type: "end", mode: "close" }) + }, + disconnect(message) { + return command({ type: "end", mode: "disconnect", message }) + }, + error(message) { + return command({ type: "end", mode: "error", message }) + }, + connections() { + return command({ type: "connections" }) + }, + acknowledgements() { + return command({ type: "acknowledgements" }) + }, + } +} diff --git a/packages/app/e2e/utils/visual-stability.ts b/packages/app/e2e/utils/visual-stability.ts new file mode 100644 index 000000000000..2c56864ed8dd --- /dev/null +++ b/packages/app/e2e/utils/visual-stability.ts @@ -0,0 +1,54 @@ +import type { Page, TestInfo } from "@playwright/test" +import { analyzeVisualObservations, analyzeVisualTraceByMarker } from "./visual-stability/analyzer" +import { legacyVisualPlan, type LegacyVisualStabilityOptions } from "./visual-stability/invariant" +import type { CapturedFrame, VisualStabilityTrace } from "./visual-stability/model" +import { markVisualProbe, startVisualProbe, stopVisualProbe } from "./visual-stability/probe" +import type { VisualRegionDefinition } from "./visual-stability/regions" +import { reportVisualStability } from "./visual-stability/reporter" + +export * from "./visual-stability/index" + +const capturedFrames = Symbol("capturedFrames") + +export async function startVisualStabilityProbe(page: Page, regions: Record) { + await startVisualProbe(page, regions) +} + +export async function stopVisualStabilityProbe(page: Page) { + const result = await stopVisualProbe(page) + const trace: VisualStabilityTrace = { markers: result.markers, samples: result.samples } + Object.defineProperty(trace, capturedFrames, { value: result.frames }) + return trace +} + +export async function markVisualStability(page: Page, label: string) { + await markVisualProbe(page, label) +} + +export function analyzeVisualStability(trace: VisualStabilityTrace, options: LegacyVisualStabilityOptions = {}) { + return analyzeVisualObservations(trace.samples, legacyVisualPlan(options)) +} + +export function analyzeVisualStabilityByMarker( + trace: VisualStabilityTrace, + options: LegacyVisualStabilityOptions = {}, +) { + return analyzeVisualTraceByMarker(trace, legacyVisualPlan(options)) +} + +export async function expectVisualStability( + testInfo: TestInfo, + name: string, + trace: VisualStabilityTrace, + options: LegacyVisualStabilityOptions = {}, +) { + await reportVisualStability( + testInfo, + name, + { + ...trace, + frames: (trace as VisualStabilityTrace & { [capturedFrames]?: CapturedFrame[] })[capturedFrames] ?? [], + }, + legacyVisualPlan(options), + ) +} diff --git a/packages/app/e2e/utils/visual-stability/analyzer.ts b/packages/app/e2e/utils/visual-stability/analyzer.ts new file mode 100644 index 000000000000..902d222e1da8 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/analyzer.ts @@ -0,0 +1,209 @@ +import type { VisualInvariant, VisualPlan } from "./invariant" +import type { VisualObservation, VisualStabilityTrace } from "./model" + +export function analyzeVisualObservations( + observations: readonly VisualObservation[], + plan: VisualPlan, +) { + const issues: string[] = [] + const invariants = plan.invariants + const names = [...new Set(observations.flatMap((sample) => Object.keys(sample.regions) as RegionName[]))] + const required = regions(invariants, "required") + const continuousAny = invariants.filter( + (invariant): invariant is Extract, { type: "continuous-any" }> => + invariant.type === "continuous-any", + ) + const unique = new Set(regions(invariants, "unique")) + const stable = new Set(regions(invariants, "stable")) + const fixed = invariants.filter( + (invariant): invariant is Extract, { type: "fixed" }> => invariant.type === "fixed", + ) + const opacity = invariants.filter( + (invariant): invariant is Extract, { type: "opacity" }> => invariant.type === "opacity", + ) + const continuity = invariants.filter( + (invariant): invariant is Extract, { type: "continuity" }> => + invariant.type === "continuity", + ) + const motion = invariants.filter( + (invariant): invariant is Extract, { type: "motion" }> => invariant.type === "motion", + ) + const labelStability = invariants.filter( + (invariant): invariant is Extract, { type: "label-stability" }> => + invariant.type === "label-stability", + ) + + for (const name of new Set(required)) { + if (!observations.some((sample) => sample.regions[name]?.visible)) issues.push(`${name} never rendered`) + } + for (const invariant of continuousAny) { + if (!invariant.regions.some((name) => observations.some((sample) => sample.regions[name]?.visible))) + issues.push(`${invariant.regions.join(" | ")} never rendered`) + } + + for (const name of names) { + const samples = observations.flatMap((observation) => { + const region = observation.regions[name] + if (!region) return [] + const clipped = + observation.viewport && (region.bottom <= observation.viewport.top || region.top >= observation.viewport.bottom) + return [{ at: observation.at, ...region, visible: region.visible && !clipped }] + }) + const visible = samples.filter((sample) => sample.visible) + if (visible.length === 0) continue + if (unique.has(name)) { + const duplicate = samples.find((sample) => sample.count > 1) + if (duplicate) issues.push(`${name} appeared ${duplicate.count} times at ${Math.round(duplicate.at)}ms`) + } + if (stable.has(name)) { + const identities = [...new Set(visible.map((sample) => sample.node).filter((node) => node > 0))] + if (identities.length > 1) issues.push(`${name} remounted ${identities.length - 1} times`) + } + for (const invariant of fixed.filter((invariant) => includes(invariant.regions, name))) { + const origin = visible[0] + const movement = origin ? Math.max(0, ...visible.map((sample) => Math.abs(sample.top - origin.top))) : 0 + if (movement > (invariant.tolerance ?? 1)) + issues.push(`${name} moved ${Math.round(movement * 10) / 10}px in the viewport`) + } + for (const invariant of opacity.filter((invariant) => includes(invariant.regions, name))) { + for (const sample of visible) { + if (sample.opacity < (invariant.floor ?? 0.65)) + issues.push(`${name} opacity fell to ${sample.opacity} at ${Math.round(sample.at)}ms`) + } + } + if (continuity.some((invariant) => includes(invariant.regions, name))) { + const firstPresent = samples.findIndex((sample) => sample.present) + const lastPresent = samples.findLastIndex((sample) => sample.present) + if (samples.slice(firstPresent, lastPresent + 1).some((sample) => !sample.present)) + issues.push(`${name} disappeared between present frames`) + const firstVisible = samples.findIndex((sample) => sample.visible) + const lastVisible = samples.findLastIndex((sample) => sample.visible) + if ( + firstVisible >= 0 && + samples.slice(firstVisible, lastVisible + 1).some((sample) => !sample.visible && sample.inViewport) + ) + issues.push(`${name} blanked between visible frames`) + } + for (const invariant of motion.filter((invariant) => includes(invariant.regions, name))) { + for (const metric of ["top", "bottom", "width", "height"] as const) { + const directions = visible + .slice(1) + .map((sample, index) => sample[metric] - visible[index]![metric]) + .filter((delta) => Math.abs(delta) > (invariant.tolerance ?? 1)) + .map(Math.sign) + const reversals = directions.slice(1).filter((direction, index) => direction !== directions[index]).length + const allowed = + metric === "top" || metric === "bottom" + ? (invariant.maxPositionReversals ?? invariant.maxReversals ?? 1) + : (invariant.maxReversals ?? 1) + if (reversals > allowed) issues.push(`${name} ${metric} reversed ${reversals} times`) + } + } + if (labelStability.some((invariant) => includes(invariant.regions, name))) { + const labels = samples + .map((sample) => sample.label) + .filter((label) => label.length > 0) + .filter((label, index, all) => label !== all[index - 1]) + if (labels.some((label, index) => labels.indexOf(label) !== index)) + issues.push(`${name} label reverted: ${labels.join(" -> ")}`) + } + } + + if (invariants.some((invariant) => invariant.type === "preserve-bottom-anchor")) { + const viewports = observations.flatMap((sample) => (sample.viewport ? [sample.viewport] : [])) + if (viewports[0] && viewports[0].distanceFromBottom <= 4) { + const lost = viewports.find((viewport) => viewport.distanceFromBottom > 4) + if (lost) issues.push(`bottom anchor moved to ${lost.distanceFromBottom}px`) + } + } + if (invariants.some((invariant) => invariant.type === "acquire-bottom-anchor")) { + const final = observations.findLast((sample) => sample.viewport)?.viewport + if (!final || final.distanceFromBottom > 4) + issues.push(`did not acquire bottom anchor${final ? ` (${final.distanceFromBottom}px away)` : ""}`) + } + + for (const invariant of continuousAny) { + const active = observations.map((sample) => invariant.regions.some((name) => sample.regions[name]?.visible)) + const first = active.indexOf(true) + const last = active.lastIndexOf(true) + if (first >= 0 && active.slice(first, last + 1).some((value) => !value)) + issues.push(`${invariant.regions.join(" | ")} blanked between visible frames`) + } + + for (const invariant of invariants.filter( + (item): item is Extract, { type: "flow" }> => item.type === "flow", + )) { + for (const [before, after] of invariant.regions + .slice(1) + .map((after, index) => [invariant.regions[index]!, after])) { + let maximum: { overlap: number; at: number } | undefined + let inverted: { at: number } | undefined + for (const sample of observations) { + const first = sample.regions[before] + const second = sample.regions[after] + if (!first?.visible || !second?.visible) continue + if ( + sample.viewport && + (first.bottom <= sample.viewport.top || + first.top >= sample.viewport.bottom || + second.bottom <= sample.viewport.top || + second.top >= sample.viewport.bottom) + ) + continue + const overlap = first.bottom - second.top + if (first.top > second.top && !inverted) inverted = { at: sample.at } + if (overlap > (invariant.overlapTolerance ?? 0.5) && (!maximum || overlap > maximum.overlap)) + maximum = { overlap, at: sample.at } + } + if (inverted) issues.push(`${before} rendered after ${after} at ${Math.round(inverted.at)}ms`) + if (maximum) + issues.push( + `${before} overlapped ${after} by ${Math.round(maximum.overlap * 10) / 10}px at ${Math.round(maximum.at)}ms`, + ) + } + } + return [...new Set(issues)] +} + +export function analyzeVisualTraceByMarker( + trace: VisualStabilityTrace, + plan: VisualPlan, +) { + if (trace.markers.length === 0) return analyzeVisualObservations(trace.samples, plan) + const required = [...new Set(plan.markerRequired ?? regions(plan.invariants, "required"))].flatMap((name) => + trace.samples.some((sample) => sample.regions[name]?.visible) ? [] : [`${name} never rendered`], + ) + const withoutRequired = plan.invariants.filter((invariant) => invariant.type !== "required") + const windows = trace.markers.flatMap((marker, index) => { + const end = trace.markers[index + 1]?.at ?? Infinity + const before = trace.samples.findLast((sample) => sample.at < marker.at) + const samples = [ + ...(before ? [before] : []), + ...trace.samples.filter((sample) => sample.at >= marker.at && sample.at < end), + ] + if (samples.length < 2) return [] + return analyzeVisualObservations(samples, { ...plan, perMarker: false, invariants: withoutRequired }).map( + (issue) => `${marker.label}: ${issue}`, + ) + }) + const aggregateMotion = + plan.aggregateMotion === false + ? [] + : analyzeVisualObservations(trace.samples, { + invariants: plan.invariants.filter((invariant) => invariant.type === "motion"), + }).filter((issue) => / (?:top|bottom|width|height) reversed \d+ times$/.test(issue)) + return [...new Set([...required, ...aggregateMotion, ...windows])] +} + +function regions["type"]>( + invariants: readonly VisualInvariant[], + type: Type, +) { + return invariants.flatMap((invariant) => + invariant.type === type && "regions" in invariant && invariant.regions !== "all" ? [...invariant.regions] : [], + ) as RegionName[] +} + +function includes(regions: readonly RegionName[] | "all", name: RegionName) { + return regions === "all" || regions.includes(name) +} diff --git a/packages/app/e2e/utils/visual-stability/capture.ts b/packages/app/e2e/utils/visual-stability/capture.ts new file mode 100644 index 000000000000..3bb583a9d5a4 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/capture.ts @@ -0,0 +1,51 @@ +import type { CDPSession, Page } from "@playwright/test" +import type { CapturedFrame } from "./model" + +export type VisualCapture = { + session: CDPSession + frames: CapturedFrame[] + startedAtEpoch: number + running: boolean + capture: Promise +} + +export async function startVisualCapture(page: Page, startedAtEpoch: number) { + if (process.env.OPENCODE_STABILITY_CAPTURE !== "1") return + const session = await page.context().newCDPSession(page) + await session.send("Page.enable") + const recording: VisualCapture = { + session, + frames: [], + startedAtEpoch, + running: true, + capture: Promise.resolve(), + } + recording.capture = (async () => { + try { + while (recording.running && recording.frames.length < 900) { + const frame = await session.send("Page.captureScreenshot", { + format: "jpeg", + quality: 80, + captureBeyondViewport: false, + optimizeForSpeed: true, + }) + recording.frames.push({ at: Date.now() - recording.startedAtEpoch, data: frame.data }) + await new Promise((resolve) => setTimeout(resolve, 50)) + } + } catch { + recording.running = false + } + })() + return recording +} + +export async function stopVisualCapture(recording: VisualCapture | undefined) { + if (!recording) return [] + recording.running = false + try { + await recording.capture + } finally { + await recording.session.detach().catch(() => undefined) + } + return recording.frames +} diff --git a/packages/app/e2e/utils/visual-stability/index.ts b/packages/app/e2e/utils/visual-stability/index.ts new file mode 100644 index 000000000000..6fdbf4acfc60 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/index.ts @@ -0,0 +1,8 @@ +export * from "./analyzer" +export * from "./capture" +export * from "./invariant" +export * from "./model" +export * from "./probe" +export * from "./regions" +export * from "./reporter" +export * from "./scenario" diff --git a/packages/app/e2e/utils/visual-stability/invariant.ts b/packages/app/e2e/utils/visual-stability/invariant.ts new file mode 100644 index 000000000000..17f6ef191361 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/invariant.ts @@ -0,0 +1,112 @@ +import type { VisualRegionDefinition } from "./regions" + +type RegionSet = readonly RegionName[] | "all" + +export type VisualInvariant = + | { type: "required"; regions: readonly RegionName[] } + | { type: "continuous-any"; regions: readonly RegionName[] } + | { type: "unique"; regions: readonly RegionName[] } + | { type: "stable"; regions: readonly RegionName[] } + | { type: "fixed"; regions: readonly RegionName[]; tolerance?: number } + | { type: "opacity"; regions: RegionSet; floor?: number } + | { + type: "motion" + regions: RegionSet + tolerance?: number + maxReversals?: number + maxPositionReversals?: number + } + | { type: "continuity"; regions: RegionSet } + | { type: "label-stability"; regions: RegionSet } + | { type: "flow"; regions: readonly RegionName[]; overlapTolerance?: number } + | { type: "preserve-bottom-anchor" } + | { type: "acquire-bottom-anchor" } + +export type VisualPlan = { + regionNames?: readonly RegionName[] + invariants: readonly VisualInvariant[] + markerRequired?: readonly RegionName[] + perMarker?: boolean + aggregateMotion?: boolean +} + +export type LegacyVisualStabilityOptions = { + flow?: RegionName[] + motionTolerance?: number + opacityFloor?: number + overlapTolerance?: number + maxReversals?: number + maxPositionReversals?: number + stable?: RegionName[] + fixed?: RegionName[] + motion?: RegionName[] + unique?: RegionName[] + preserveBottomAnchor?: boolean + acquireBottomAnchor?: boolean + perMarker?: boolean + continuousAny?: RegionName[][] + required?: RegionName[] + aggregateMotion?: boolean + inferRequired?: boolean +} + +export function visualPlan>( + regions: Regions, + invariants: readonly VisualInvariant>[], + options: Omit>, "regionNames" | "invariants"> = {}, +): VisualPlan> { + return { ...options, regionNames: Object.keys(regions) as Extract[], invariants } +} + +export function legacyVisualPlan( + options: LegacyVisualStabilityOptions = {}, +): VisualPlan { + const inferred = + options.inferRequired === false + ? [] + : [ + ...(options.stable ?? []), + ...(options.fixed ?? []), + ...(options.unique ?? []), + ...(options.motion ?? []), + ...(options.flow ?? []), + ] + return { + perMarker: options.perMarker, + aggregateMotion: options.aggregateMotion, + markerRequired: [ + ...(options.required ?? []), + ...(options.stable ?? []), + ...(options.fixed ?? []), + ...(options.unique ?? []), + ...(options.motion ?? []), + ...(options.flow ?? []), + ], + invariants: [ + { type: "required", regions: [...(options.required ?? []), ...inferred] }, + ...(options.continuousAny ?? []).map( + (regions): VisualInvariant => ({ type: "continuous-any", regions }), + ), + ...(options.unique ? [{ type: "unique" as const, regions: options.unique }] : []), + ...(options.stable ? [{ type: "stable" as const, regions: options.stable }] : []), + ...(options.fixed + ? [{ type: "fixed" as const, regions: options.fixed, tolerance: options.motionTolerance }] + : []), + { type: "opacity", regions: "all", floor: options.opacityFloor }, + { type: "continuity", regions: "all" }, + { + type: "motion", + regions: options.motion ?? "all", + tolerance: options.motionTolerance, + maxReversals: options.maxReversals, + maxPositionReversals: options.maxPositionReversals, + }, + { type: "label-stability", regions: "all" }, + ...(options.preserveBottomAnchor ? [{ type: "preserve-bottom-anchor" as const }] : []), + ...(options.acquireBottomAnchor ? [{ type: "acquire-bottom-anchor" as const }] : []), + ...(options.flow + ? [{ type: "flow" as const, regions: options.flow, overlapTolerance: options.overlapTolerance }] + : []), + ], + } +} diff --git a/packages/app/e2e/utils/visual-stability/model.ts b/packages/app/e2e/utils/visual-stability/model.ts new file mode 100644 index 000000000000..1b966638c9ce --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/model.ts @@ -0,0 +1,47 @@ +export type VisualRegionSample = { + present: boolean + visible: boolean + inViewport: boolean + cssHidden?: boolean + top: number + bottom: number + layoutTop?: number + layoutBottom?: number + width: number + height: number + opacity: number + count: number + node: number + label: string + text: string +} + +export type VisualViewportSample = { + top: number + bottom: number + scrollTop: number + scrollHeight: number + clientHeight: number + distanceFromBottom: number +} + +export type VisualObservation = { + at: number + regions: string extends RegionName + ? Record + : Partial> + viewport?: VisualViewportSample +} + +export type VisualMarker = { at: number; label: string } + +export type VisualStabilityTrace = { + markers: VisualMarker[] + samples: VisualObservation[] +} + +export type CapturedFrame = { at: number; data: string } + +export type VisualProbeResult = VisualStabilityTrace & { + frames: CapturedFrame[] +} diff --git a/packages/app/e2e/utils/visual-stability/probe.ts b/packages/app/e2e/utils/visual-stability/probe.ts new file mode 100644 index 000000000000..abf274d3592a --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/probe.ts @@ -0,0 +1,226 @@ +import type { Page } from "@playwright/test" +import { startVisualCapture, stopVisualCapture, type VisualCapture } from "./capture" +import type { VisualMarker, VisualObservation, VisualProbeResult } from "./model" +import type { VisualRegionDefinition } from "./regions" + +type ProbeWindow = Window & { + __visualStabilityProbe?: { + startedAt: number + markers: VisualMarker[] + samples: VisualObservation[] + stop: () => void + } +} + +const captures = new WeakMap() + +export async function startVisualProbe>( + page: Page, + regions: Regions, +) { + await stopCapture(page) + await page.evaluate(() => { + ;(window as ProbeWindow).__visualStabilityProbe?.stop() + }) + const startedAtEpoch = await page.evaluate((regions) => { + const samples: VisualObservation[] = [] + const markers: VisualMarker[] = [] + const startedAt = performance.now() + const nodes = new WeakMap() + const lastBounds = new Map() + let nextNode = 1 + let running = true + const round = (value: number) => Math.round(value * 10) / 10 + const opacity = (element: Element) => Number(getComputedStyle(element).opacity) + const sample = () => { + if (!running) return + setTimeout(() => { + if (!running) return + const viewport = [...document.querySelectorAll(".scroll-view__viewport")].find((element) => + element.querySelector("[data-timeline-row]"), + ) + const viewportRect = viewport?.getBoundingClientRect() + samples.push({ + at: performance.now() - startedAt, + viewport: viewport + ? { + top: round(viewportRect!.top), + bottom: round(viewportRect!.bottom), + scrollTop: round(viewport.scrollTop), + scrollHeight: round(viewport.scrollHeight), + clientHeight: round(viewport.clientHeight), + distanceFromBottom: round(viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop), + } + : undefined, + regions: Object.fromEntries( + Object.entries(regions).map(([name, config]) => { + const found = document.querySelector(config.selector) + const count = document.querySelectorAll(config.selector).length + const element = config.closest ? found?.closest(config.closest) : found + if (!element) + return [ + name, + { + present: false, + visible: false, + inViewport: false, + top: 0, + bottom: 0, + width: 0, + height: 0, + opacity: 0, + count, + node: 0, + label: "", + text: "", + }, + ] + const rect = element.getBoundingClientRect() + const style = getComputedStyle(element) + if (rect.height > 0) lastBounds.set(name, { top: rect.top, bottom: rect.bottom }) + const known = rect.height > 0 ? rect : lastBounds.get(name) + const painted = (() => { + const result = { top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right } + let parent = element.parentElement + while (parent) { + const parentStyle = getComputedStyle(parent) + if (["hidden", "clip", "scroll", "auto"].includes(parentStyle.overflowY)) { + const parentRect = parent.getBoundingClientRect() + result.top = Math.max(result.top, parentRect.top) + result.bottom = Math.min(result.bottom, parentRect.bottom) + } + if (["hidden", "clip", "scroll", "auto"].includes(parentStyle.overflowX)) { + const parentRect = parent.getBoundingClientRect() + result.left = Math.max(result.left, parentRect.left) + result.right = Math.min(result.right, parentRect.right) + } + if (parent === viewport) break + parent = parent.parentElement + } + if (viewportRect) { + result.top = Math.max(result.top, viewportRect.top) + result.bottom = Math.min(result.bottom, viewportRect.bottom) + result.left = Math.max(result.left, viewportRect.left) + result.right = Math.min(result.right, viewportRect.right) + } + return result + })() + const contentOpacity = config.opacitySelectors?.length + ? Math.max( + 0, + ...config.opacitySelectors.flatMap((selector) => + [...element.querySelectorAll(selector)].map((node) => { + let value = 1 + let current: Element | null = node + while (current) { + value *= opacity(current) + if (current === element) break + current = current.parentElement + } + return value + }), + ), + ) + : opacity(element) + let visibleOpacity = contentOpacity + let ancestor = element.parentElement + let ancestorHidden = false + while (ancestor) { + const ancestorStyle = getComputedStyle(ancestor) + visibleOpacity *= Number(ancestorStyle.opacity) + if (ancestorStyle.display === "none" || ancestorStyle.visibility === "hidden") ancestorHidden = true + if (ancestor === viewport) break + ancestor = ancestor.parentElement + } + const cssHidden = + ancestorHidden || style.display === "none" || style.visibility === "hidden" || visibleOpacity === 0 + return [ + name, + { + present: true, + visible: + style.display !== "none" && + style.visibility !== "hidden" && + visibleOpacity > 0 && + painted.right > painted.left && + painted.bottom > painted.top, + inViewport: + !viewportRect || (!!known && known.bottom > viewportRect.top && known.top < viewportRect.bottom), + cssHidden, + top: round(painted.top), + bottom: round(painted.bottom), + layoutTop: round(rect.top), + layoutBottom: round(rect.bottom), + width: round(painted.right - painted.left), + height: round(painted.bottom - painted.top), + opacity: round(visibleOpacity), + count, + node: (() => { + const current = nodes.get(element) + if (current) return current + nodes.set(element, nextNode) + return nextNode++ + })(), + label: element.getAttribute("aria-label") ?? "", + text: (element.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 500), + }, + ] + }), + ), + }) + requestAnimationFrame(sample) + }, 0) + } + ;(window as ProbeWindow).__visualStabilityProbe = { + startedAt, + markers, + samples, + stop: () => { + running = false + }, + } + requestAnimationFrame(sample) + return new Promise((resolve) => { + const ready = () => { + if (samples.length > 0) return resolve(performance.timeOrigin + startedAt) + requestAnimationFrame(ready) + } + ready() + }) + }, regions) + const capture = await startVisualCapture(page, startedAtEpoch) + if (capture) captures.set(page, capture) +} + +export async function stopVisualProbe( + page: Page, +): Promise> { + return page + .evaluate(() => { + const probe = (window as ProbeWindow).__visualStabilityProbe + if (!probe) throw new Error("Visual stability probe is not running") + probe.stop() + return { markers: probe.markers, samples: probe.samples } + }) + .then( + async (trace) => ({ ...trace, frames: await stopCapture(page) }) as unknown as VisualProbeResult, + async (error: unknown) => { + await stopCapture(page) + throw error + }, + ) +} + +export async function markVisualProbe(page: Page, label: string) { + await page.evaluate((label) => { + const probe = (window as ProbeWindow).__visualStabilityProbe + if (!probe) return + probe.markers.push({ at: performance.now() - probe.startedAt, label }) + }, label) +} + +async function stopCapture(page: Page) { + const capture = captures.get(page) + if (capture) captures.delete(page) + return stopVisualCapture(capture) +} diff --git a/packages/app/e2e/utils/visual-stability/regions.ts b/packages/app/e2e/utils/visual-stability/regions.ts new file mode 100644 index 000000000000..4c0b34e78a2a --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/regions.ts @@ -0,0 +1,18 @@ +export type VisualRegionDefinition = { + selector: string + closest?: string + opacitySelectors?: readonly string[] +} + +export function defineVisualRegions>(regions: Regions) { + return regions +} + +export function mapVisualRegions, Result>( + regions: Regions, + map: (region: Regions[keyof Regions], name: keyof Regions) => Result, +) { + return Object.fromEntries( + Object.entries(regions).map(([name, region]) => [name, map(region as Regions[keyof Regions], name)]), + ) as { [Name in keyof Regions]: Result } +} diff --git a/packages/app/e2e/utils/visual-stability/reporter.ts b/packages/app/e2e/utils/visual-stability/reporter.ts new file mode 100644 index 000000000000..db283072a846 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/reporter.ts @@ -0,0 +1,63 @@ +import { expect, type TestInfo } from "@playwright/test" +import { writeFile } from "node:fs/promises" +import { analyzeVisualObservations, analyzeVisualTraceByMarker } from "./analyzer" +import type { VisualPlan } from "./invariant" +import type { VisualProbeResult } from "./model" + +export async function reportVisualStability( + testInfo: TestInfo, + name: string, + result: VisualProbeResult, + plan: VisualPlan, +) { + const trace = { markers: result.markers, samples: result.samples } + const issues = plan.perMarker + ? analyzeVisualTraceByMarker(trace, plan) + : analyzeVisualObservations(result.samples, plan) + const tracePath = testInfo.outputPath(`${name}-visual-trace.json`) + const issuesPath = testInfo.outputPath(`${name}-visual-issues.json`) + await writeFile(tracePath, JSON.stringify(trace, null, 2)) + await writeFile( + issuesPath, + JSON.stringify({ issues, markers: result.markers, capturedFrameCount: result.frames.length }, null, 2), + ) + await testInfo.attach(`${name}-visual-trace`, { path: tracePath, contentType: "application/json" }) + await testInfo.attach(`${name}-visual-issues`, { path: issuesPath, contentType: "application/json" }) + if (issues.length) await attachViolationFrames(testInfo, name, result, issues) + expect(issues, `${name}: ${issues.join("\n")}`).toEqual([]) +} + +async function attachViolationFrames( + testInfo: TestInfo, + name: string, + result: VisualProbeResult, + issues: string[], +) { + if (result.frames.length === 0) return + const targets = [ + ...new Set( + issues.flatMap((issue) => { + const match = issue.match(/ at (\d+)ms/) + if (match) return [Number(match[1])] + const marker = result.markers.find((item) => issue.startsWith(`${item.label}:`)) + return marker ? [marker.at] : [] + }), + ), + ].slice(0, 6) + for (const [violation, target] of targets.entries()) { + const nearest = result.frames.reduce( + (best, frame, index) => (Math.abs(frame.at - target) < Math.abs(result.frames[best]!.at - target) ? index : best), + 0, + ) + for (const [label, index] of [ + ["before", Math.max(0, nearest - 1)], + ["violation", nearest], + ["after", Math.min(result.frames.length - 1, nearest + 1)], + ] as const) { + await testInfo.attach(`${name}-${violation + 1}-${label}-${Math.round(result.frames[index]!.at)}ms`, { + body: Buffer.from(result.frames[index]!.data, "base64"), + contentType: "image/jpeg", + }) + } + } +} diff --git a/packages/app/e2e/utils/visual-stability/scenario.ts b/packages/app/e2e/utils/visual-stability/scenario.ts new file mode 100644 index 000000000000..213630207144 --- /dev/null +++ b/packages/app/e2e/utils/visual-stability/scenario.ts @@ -0,0 +1,20 @@ +import type { Page, TestInfo } from "@playwright/test" +import type { VisualPlan } from "./invariant" +import { startVisualProbe, stopVisualProbe } from "./probe" +import type { VisualRegionDefinition } from "./regions" +import { reportVisualStability } from "./reporter" + +export async function runVisualStabilityScenario>(input: { + page: Page + testInfo: TestInfo + name: string + regions: Regions + plan: VisualPlan> + run: () => Promise +}) { + await startVisualProbe(input.page, input.regions) + await input.run() + const result = await stopVisualProbe>(input.page) + await reportVisualStability(input.testInfo, input.name, result, input.plan) + return result +} diff --git a/packages/app/package.json b/packages/app/package.json index b8f52ebfc75d..510e2015dbe5 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.17.11", + "version": "1.17.18", "description": "", "type": "module", "exports": { @@ -13,6 +13,7 @@ }, "scripts": { "typecheck": "tsgo -b", + "typecheck:e2e": "tsgo -p e2e/tsconfig.json", "start": "vite", "dev": "vite", "build": "vite build", @@ -25,6 +26,7 @@ "test:e2e:local": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:report": "playwright show-report e2e/playwright-report", + "test:stability": "bun test ./e2e/performance/unit/visual-stability.test.ts && playwright test --config e2e/performance/timeline-stability/playwright.config.ts", "test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts" }, "license": "MIT", @@ -38,18 +40,21 @@ "@types/luxon": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", + "tw-animate-css": "1.4.0", "typescript": "catalog:", "vite": "catalog:", "vite-plugin-icons-spritesheet": "3.0.1", "vite-plugin-solid": "catalog:" }, "dependencies": { + "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", "@dnd-kit/helpers": "0.5.0", "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -84,6 +89,7 @@ "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", + "solid-presence": "0.2.0", "tailwindcss": "catalog:" } } diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 6a1c8f268dc5..86263d172d0a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -9,9 +9,10 @@ import { Font } from "@opencode-ai/ui/font" import { Splash } from "@opencode-ai/ui/logo" import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { MetaProvider } from "@solidjs/meta" -import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" +import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { Effect } from "effect" +import { base64Encode } from "@opencode-ai/core/util/encode" import { type Component, createEffect, @@ -28,10 +29,10 @@ import { Show, } from "solid-js" import { Dynamic } from "solid-js/web" -import { CommandProvider } from "@/context/command" +import { CommandProvider, useCommand, type CommandOption } from "@/context/command" import { CommentsProvider } from "@/context/comments" import { FileProvider } from "@/context/file" -import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk" +import { ServerSDKProvider } from "@/context/server-sdk" import { ServerSyncProvider, useServerSync } from "@/context/server-sync" import { GlobalProvider, useGlobal } from "@/context/global" import { HighlightsProvider } from "@/context/highlights" @@ -40,10 +41,10 @@ import { LayoutProvider } from "@/context/layout" import { ModelsProvider } from "@/context/models" import { NotificationProvider } from "@/context/notification" import { PermissionProvider } from "@/context/permission" +import { usePlatform } from "@/context/platform" import { PromptProvider } from "@/context/prompt" import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server" import { SettingsProvider, useSettings } from "@/context/settings" -import { TerminalProvider } from "@/context/terminal" import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs" import { SDKProvider, useSDK } from "@/context/sdk" import { WslServersProvider } from "@/wsl/context" @@ -52,16 +53,10 @@ import LegacyLayout from "@/pages/layout" import NewLayout from "@/pages/layout-new" import { ErrorPage } from "./pages/error" import { useCheckServerHealth } from "./utils/server-health" -import { - legacySessionHref, - legacySessionServer, - requireServerKey, - selectSessionLineage, - sessionHref, -} from "./utils/session-route" -import { isSessionNotFoundError } from "./utils/server-errors" - -import Session from "@/pages/session" +import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route" +import { createSessionLineage } from "@/pages/session/session-lineage" + +import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session" import { NewHome, LegacyHome } from "@/pages/home" const NewSession = lazy(() => import("@/pages/new-session")) @@ -96,13 +91,13 @@ const SessionRoute = () => { }) return ( - - - + + + ) } -const TargetSessionRoute = () => { +function TargetServerRoute(props: ParentProps) { const params = useParams<{ serverKey: string; id: string }>() const global = useGlobal() const conn = createMemo(() => { @@ -111,77 +106,50 @@ const TargetSessionRoute = () => { }) return ( + // Owns the server-identity remount. Session changes must NOT remount this + // subtree (SessionRouteErrorBoundary resets and createSessionLineage + // re-resolves reactively instead); both rely on this key for server changes. - - - + {props.children} ) } -function ResolvedTargetSessionRoute() { +const TargetSessionRoute = () => ( + + + +) + +function LegacyTargetSessionRoute() { const params = useParams<{ serverKey: string; id: string }>() - const settings = useSettings() - const tabs = useTabs() + return ( + + + + + + ) +} + +function LegacyTargetSessionRedirect() { + const params = useParams<{ id: string }>() + const navigate = useNavigate() const sync = useServerSync() - const serverKey = createMemo(() => requireServerKey(params.serverKey)) - const cached = createMemo(() => sync().session.lineage.peek(params.id)) - const [resolved] = createResource( - () => { - if (cached()) return - return { id: params.id, server: serverKey(), sync: sync() } - }, - ({ id, server, sync }) => - sync.session.lineage.resolve(id).catch((error) => { - if (isSessionNotFoundError(error, id)) tabs.removeSessionTab({ server, sessionId: id }) - throw error - }), + const current = createSessionLineage( + () => params.id, + () => sync().session.lineage, ) - const current = createMemo(() => selectSessionLineage(params.id, cached(), resolved())) - const directory = createMemo(() => current()?.session.directory) - const targetDirectory = () => directory()! createEffect(() => { - const session = current() - if (!session) return - tabs.addSessionTab({ - server: serverKey(), - sessionId: session.root.id, - }) + const directory = current()?.session.directory + if (!directory) return + navigate(legacySessionHref(directory, params.id), { replace: true }) }) - return ( - params.id}> - }> - - } - > - - - - - - - - - - ) -} - -function TargetSessionPage() { - const sdk = useSDK() - const serverSDK = useServerSDK() - return ( - - - - - - ) + return null } // Wraps the non-draft routes. They are gated on (and keyed to) the globally selected @@ -197,16 +165,17 @@ function SelectedServerProviders(props: ParentProps) { ) } -function LegacyServerLayout(props: ParentProps) { +function LegacyServerLayout(props: ParentProps<{ serverScoped?: JSX.Element }>) { return ( - {props.children} + {props.children} ) } function DraftRoute() { const [search] = useSearchParams<{ draftId?: string }>() + const settings = useSettings() const tabs = useTabs() return ( @@ -215,7 +184,14 @@ function DraftRoute() { keyed fallback={} > - {(draft) => } + {(draft) => ( + } + > + + + )} ) @@ -231,7 +207,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) { - + @@ -239,7 +215,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) { - + @@ -257,7 +233,7 @@ declare global { deepLinks?: string[] } api?: { - setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise + setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise exportDebugLogs?: () => Promise } } @@ -300,25 +276,49 @@ function SharedProviders(props: ParentProps) { <> + {props.children} ) } +function DesktopCommands() { + const command = useCommand() + const language = useLanguage() + const platform = usePlatform() + + command.register("desktop", () => { + const commands: CommandOption[] = [] + if (platform.platform === "desktop" && platform.exportDebugLogs) { + commands.push({ + id: "logs.export", + title: "Export logs", + category: language.t("command.category.settings"), + onSelect: () => { + void platform.exportDebugLogs?.() + }, + }) + } + return commands + }) + + return null +} + // Server-scoped providers shared by the legacy shell and the top-level new shell. type ServerScopedShellProps = ParentProps<{ directory?: () => string | undefined sessionID?: () => string | undefined + serverScoped?: JSX.Element }> function ServerScopedProviders(props: ServerScopedShellProps) { return ( - - {props.children} - + {props.serverScoped} + {props.children} ) @@ -326,44 +326,30 @@ function ServerScopedProviders(props: ServerScopedShellProps) { function LegacyServerScopedShell(props: ServerScopedShellProps) { return ( - + {props.children} ) } -function NewAppLayout(props: ParentProps) { +function NewAppLayout(props: ParentProps<{ serverScoped?: JSX.Element }>) { return ( - + {props.children} ) } -function TargetServerScopedProviders(props: ServerScopedShellProps) { +function DraftServerScopedProviders(props: ParentProps<{ directory?: () => string | undefined }>) { return ( - - {props.children} - + {props.children} ) } -function SessionProviders(props: ParentProps) { - return ( - - - - {props.children} - - - - ) -} - // The draft page only renders the prompt composer, so it drops TerminalProvider. // FileProvider and CommentsProvider stay because PromptInput uses file search and comment context. function DraftProviders(props: ParentProps) { @@ -381,8 +367,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { { - void window.api?.setTitlebar?.({ mode }) + onThemeApplied={(_, mode, scheme) => { + void window.api?.setTitlebar?.({ mode, scheme }) }} > @@ -410,7 +396,7 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { ) } -function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { +function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean; startup?: Promise }>) { const server = useServer() const checkServerHealth = useCheckServerHealth() @@ -439,34 +425,45 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { const checking = createMemo( () => checkMode() === "blocking" && ["unresolved", "pending"].includes(startupHealthCheck.state), ) + const [startup] = createResource(async () => { + if (!props.startup) return true + await props.startup.catch((error) => { + console.error("[startup] startup gate failed", error) + }) + return true + }) + const startupChecking = createMemo( + () => startupHealthCheck.latest === true && ["unresolved", "pending"].includes(startup.state), + ) + const loading = createMemo(() => checking() || startupChecking()) return ( - + <> + + { + if (checkMode() === "background") void healthCheckActions.refetch() + }} + onServerSelected={(key) => { + setCheckMode("blocking") + server.setActive(key) + void healthCheckActions.refetch() + }} + /> + } + > + {props.children} + + + +
- } - > - { - if (checkMode() === "background") void healthCheckActions.refetch() - }} - onServerSelected={(key) => { - setCheckMode("blocking") - server.setActive(key) - void healthCheckActions.refetch() - }} - /> - } - > - {props.children} -
+ ) } @@ -533,6 +530,8 @@ export function AppInterface(props: { servers?: Array router?: Component disableHealthCheck?: boolean + startup?: Promise + serverScoped?: JSX.Element }) { // The visual new layout lives in the router root so it remains mounted across // route changes. Draft and session routes override only their server-bound data @@ -554,21 +553,23 @@ export function AppInterface(props: { > - + ( - - - {routerProps.children} - - + + + + {routerProps.children} + + + )} > - + @@ -578,13 +579,24 @@ export function AppInterface(props: { ) } -function Routes() { +function Routes(props: { serverScoped?: JSX.Element }) { const settings = useSettings() return ( <> - - {} + ( + {routeProps.children} + )} + > + + { + <> + + + + } + } /> @@ -592,15 +604,15 @@ function Routes() { - + + - ) } -function LegacyTargetSessionRoute() { +function NewLayoutLegacySessionRedirect() { const server = useServer() const tabs = useTabs() const params = useParams<{ id: string }>() diff --git a/packages/app/src/assets/help/introducing-tabs.mp4 b/packages/app/src/assets/help/introducing-tabs.mp4 new file mode 100644 index 000000000000..2bcc8aeef6ff Binary files /dev/null and b/packages/app/src/assets/help/introducing-tabs.mp4 differ diff --git a/packages/app/src/assets/help/placeholder.png b/packages/app/src/assets/help/placeholder.png new file mode 100644 index 000000000000..c651d06cc920 Binary files /dev/null and b/packages/app/src/assets/help/placeholder.png differ diff --git a/packages/app/src/components/command-palette.ts b/packages/app/src/components/command-palette.ts new file mode 100644 index 000000000000..b61a61977eea --- /dev/null +++ b/packages/app/src/components/command-palette.ts @@ -0,0 +1,325 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { getFilename } from "@opencode-ai/core/util/path" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useNavigate } from "@solidjs/router" +import { createMemo, onCleanup } from "solid-js" +import { useCommand, type CommandOption } from "@/context/command" +import { useFile } from "@/context/file" +import { useLanguage } from "@/context/language" +import { useLayout } from "@/context/layout" +import { useServerSDK, type ServerSDK } from "@/context/server-sdk" +import { useServerSync } from "@/context/server-sync" +import { createSessionTabs } from "@/pages/session/helpers" +import { useSessionLayout } from "@/pages/session/session-layout" +import { decode64 } from "@/utils/base64" + +export type CommandPaletteEntry = { + id: string + type: "command" | "file" | "session" + title: string + description?: string + keybind?: string + category: string + option?: CommandOption + path?: string + directory?: string + sessionID?: string + archived?: number + updated?: number +} + +const ENTRY_LIMIT = 5 +const COMMON_COMMAND_IDS = [ + "session.new", + "workspace.new", + "session.previous", + "session.next", + "terminal.toggle", + "review.toggle", +] as const + +export function uniqueCommandPaletteEntries(items: CommandPaletteEntry[]) { + const seen = new Set() + return items.filter((item) => { + if (seen.has(item.id)) return false + seen.add(item.id) + return true + }) +} + +export function createCommandPaletteFileEntry(path: string, category: string): CommandPaletteEntry { + return { + id: "file:" + path, + type: "file", + title: path, + category, + path, + } +} + +export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => void) { + const file = useFile() + const layout = useLayout() + const { tabs, view } = useSessionLayout() + + return (path: string) => { + const value = file.tab(path) + void tabs().open(value) + void file.load(path) + if (!view().reviewPanel.opened()) view().reviewPanel.open() + layout.fileTree.setTab("all") + onOpenFile?.(path) + tabs().setActive(value) + } +} + +export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) { + const command = useCommand() + const language = useLanguage() + const layout = useLayout() + const file = useFile() + const dialog = useDialog() + const navigate = useNavigate() + const serverSDK = useServerSDK()() + const serverSync = useServerSync() + const { params, tabs } = useSessionLayout() + const openFile = createCommandPaletteFileOpener(props.onOpenFile) + const state = { cleanup: undefined as (() => void) | void, committed: false } + const filesOnly = () => props.filesOnly?.() ?? false + + const allowedCommands = createMemo(() => { + if (filesOnly()) return [] + return command.options.filter( + (option) => + !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open", + ) + }) + const commandEntries = createMemo(() => { + const category = language.t("palette.group.commands") + return allowedCommands().map((option) => createCommandEntry(option, category)) + }) + const preferredCommandEntries = createMemo(() => { + const all = allowedCommands() + const order = new Map(COMMON_COMMAND_IDS.map((id, index) => [id, index])) + const picked = all.filter((option) => order.has(option.id)) + const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT) + const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base + const category = language.t("palette.group.commands") + return sorted.map((option) => createCommandEntry(option, category)) + }) + + const tabState = createSessionTabs({ + tabs, + pathFromTab: file.pathFromTab, + normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab), + }) + const recentFileEntries = createMemo(() => { + const all = tabState.openedTabs() + const active = tabState.activeFileTab() + const order = active ? [active, ...all.filter((item) => item !== active)] : all + const seen = new Set() + const category = language.t("palette.group.files") + return order + .map((item) => file.pathFromTab(item)) + .filter((path): path is string => { + if (!path || seen.has(path)) return false + seen.add(path) + return true + }) + .slice(0, ENTRY_LIMIT) + .map((path) => createCommandPaletteFileEntry(path, category)) + }) + const rootFileEntries = createMemo(() => { + const category = language.t("palette.group.files") + return file.tree + .children("") + .filter((node) => node.type === "file") + .map((node) => node.path) + .sort((a, b) => a.localeCompare(b)) + .slice(0, ENTRY_LIMIT) + .map((path) => createCommandPaletteFileEntry(path, category)) + }) + + const projectDirectory = createMemo(() => decode64(params.dir) ?? "") + const project = createMemo(() => { + const directory = projectDirectory() + if (!directory) return undefined + return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory)) + }) + const workspaces = createMemo(() => { + const directory = projectDirectory() + const current = project() + if (!current) return directory ? [directory] : [] + const dirs = [current.worktree, ...(current.sandboxes ?? [])] + if (directory && !dirs.includes(directory)) return [...dirs, directory] + return dirs + }) + const homedir = createMemo(() => serverSync().data.path.home) + const sessions = createSessionEntries({ + workspaces, + label: (directory) => { + const current = project() + const kind = + current && directory === current.worktree + ? language.t("workspace.type.local") + : language.t("workspace.type.sandbox") + const [store] = serverSync().child(directory, { bootstrap: false }) + const home = homedir() + const path = home ? directory.replace(home, "~") : directory + const name = store.vcs?.branch ?? getFilename(directory) + return `${kind} : ${name || path}` + }, + load: (directory) => serverSDK.client.session.list({ directory, roots: true }), + untitled: () => language.t("command.session.new"), + category: () => language.t("command.category.session"), + }) + + const highlight = (item: CommandPaletteEntry | undefined) => { + state.cleanup?.() + state.cleanup = undefined + if (item?.type !== "command") return + state.cleanup = item.option?.onHighlight?.() + } + + const select = (item: CommandPaletteEntry | undefined) => { + if (!item) return + state.committed = true + state.cleanup = undefined + dialog.close() + if (item.type === "command") { + item.option?.onSelect?.("palette") + return + } + if (item.type === "session") { + if (!item.directory || !item.sessionID) return + navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`) + return + } + if (!item.path) return + openFile(item.path) + } + + onCleanup(() => { + if (state.committed) return + state.cleanup?.() + }) + + return { + language, + file, + commandEntries, + preferredCommandEntries, + recentFileEntries, + rootFileEntries, + sessions, + highlight, + select, + close: () => dialog.close(), + } +} + +function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry { + return { + id: "command:" + option.id, + type: "command", + title: option.title, + description: option.description, + keybind: option.keybind, + category, + option, + } +} + +function createSessionEntries(props: { + workspaces: () => string[] + label: (directory: string) => string + load: (directory: string) => ReturnType + untitled: () => string + category: () => string +}) { + const state: { + token: number + inflight: Promise | undefined + cached: CommandPaletteEntry[] | undefined + } = { token: 0, inflight: undefined, cached: undefined } + + return (text: string) => { + if (!text.trim()) { + state.token += 1 + state.inflight = undefined + state.cached = undefined + return [] as CommandPaletteEntry[] + } + if (state.cached) return state.cached + if (state.inflight) return state.inflight + + const current = state.token + const dirs = props.workspaces() + if (dirs.length === 0) return [] as CommandPaletteEntry[] + + state.inflight = Promise.all( + dirs.map((directory) => { + const description = props.label(directory) + return props + .load(directory) + .then((result) => + (result.data ?? []) + .filter((session) => !!session?.id) + .map((session) => ({ + id: session.id, + title: session.title ?? props.untitled(), + description, + directory, + archived: session.time?.archived, + updated: session.time?.updated, + })), + ) + .catch(() => [] as SessionEntryInput[]) + }), + ) + .then((results) => { + if (state.token !== current) return [] as CommandPaletteEntry[] + const seen = new Set() + const next = results + .flat() + .filter((item) => { + const key = `${item.directory}:${item.id}` + if (seen.has(key)) return false + seen.add(key) + return true + }) + .map((item) => createSessionEntry(item, props.category())) + state.cached = next + return next + }) + .catch(() => [] as CommandPaletteEntry[]) + .finally(() => { + state.inflight = undefined + }) + + return state.inflight + } +} + +type SessionEntryInput = { + directory: string + id: string + title: string + description: string + archived?: number + updated?: number +} + +function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry { + return { + id: `session:${input.directory}:${input.id}`, + type: "session", + title: input.title, + description: input.description, + category, + directory: input.directory, + sessionID: input.id, + archived: input.archived, + updated: input.updated, + } +} diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index eda025546a16..e55e128b82ff 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -3,6 +3,7 @@ import { batch, createEffect, onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" import { makeEventListener } from "@solid-primitives/event-listener" import { Tooltip } from "@opencode-ai/ui/tooltip" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { useLanguage } from "@/context/language" type Mem = Performance & { @@ -51,31 +52,62 @@ const bad = (n: number | undefined, limit: number, low = false) => { const session = (path: string) => path.includes("/session") -function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string; wide?: boolean }) { - return ( - +function Cell(props: { + bad?: boolean + dim?: boolean + inline?: boolean + label: string + tip: string + value: string + wide?: boolean +}) { + const content = () => ( +
-
{props.label}
-
- {props.value} -
+ {props.label}
+
+ {props.value} +
+
+ ) + + if (props.inline) { + return ( + + {content()} + + ) + } + + return ( + + {content()} ) } -export function DebugBar() { +export function DebugBar(props: { inline?: boolean } = {}) { const language = useLanguage() const location = useLocation() const routing = useIsRouting() @@ -101,7 +133,7 @@ export function DebugBar() { }, }) - const na = () => language.t("debugBar.na") + const na = () => language.t("debugBar.na").toUpperCase() const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined) const heapv = () => { const value = heap() @@ -363,15 +395,30 @@ export function DebugBar() { return (
) } diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 647e5002a297..fb5aa0426080 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -6,21 +6,41 @@ import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { useMutation } from "@tanstack/solid-query" import { TextField } from "@opencode-ai/ui/text-field" import { showToast } from "@/utils/toast" -import { type Accessor, batch, For } from "solid-js" +import { batch, For } from "solid-js" import { createStore, produce } from "solid-js/store" import { Link } from "@/components/link" import { useServerSDK } from "@/context/server-sdk" import { useServerSync } from "@/context/server-sync" import { useLanguage } from "@/context/language" import { type FormState, headerRow, modelRow, validateCustomProvider } from "./dialog-custom-provider-form" -import { DialogSelectProvider } from "./dialog-select-provider" type Props = { - back?: "providers" | "close" - directory?: Accessor + onBack: () => void } export function DialogCustomProvider(props: Props) { + const language = useLanguage() + + return ( + + } + transition + > + + + ) +} + +export function CustomProviderForm() { const dialog = useDialog() const serverSync = useServerSync() const serverSDK = useServerSDK() @@ -36,14 +56,6 @@ export function DialogCustomProvider(props: Props) { err: {}, }) - const goBack = () => { - if (props.back === "close") { - dialog.close() - return - } - dialog.show(() => ) - } - const addModel = () => { setForm( "models", @@ -163,168 +175,155 @@ export function DialogCustomProvider(props: Props) { } return ( - - } - transition - > -
-
- -
{language.t("provider.custom.title")}
-
+
+
+ +
{language.t("provider.custom.title")}
+
-
-

- {language.t("provider.custom.description.prefix")} - - {language.t("provider.custom.description.link")} - - {language.t("provider.custom.description.suffix")} -

+ +

+ {language.t("provider.custom.description.prefix")} + + {language.t("provider.custom.description.link")} + + {language.t("provider.custom.description.suffix")} +

-
- setField("providerID", v)} - validationState={form.err.providerID ? "invalid" : undefined} - error={form.err.providerID} - /> - setField("name", v)} - validationState={form.err.name ? "invalid" : undefined} - error={form.err.name} - /> - setField("baseURL", v)} - validationState={form.err.baseURL ? "invalid" : undefined} - error={form.err.baseURL} - /> - setField("apiKey", v)} - /> -
+
+ setField("providerID", v)} + validationState={form.err.providerID ? "invalid" : undefined} + error={form.err.providerID} + /> + setField("name", v)} + validationState={form.err.name ? "invalid" : undefined} + error={form.err.name} + /> + setField("baseURL", v)} + validationState={form.err.baseURL ? "invalid" : undefined} + error={form.err.baseURL} + /> + setField("apiKey", v)} + /> +
-
- - - {(m, i) => ( -
-
- setModel(i(), "id", v)} - validationState={m.err.id ? "invalid" : undefined} - error={m.err.id} - /> -
-
- setModel(i(), "name", v)} - validationState={m.err.name ? "invalid" : undefined} - error={m.err.name} - /> -
- removeModel(i())} - disabled={form.models.length <= 1} - aria-label={language.t("provider.custom.models.remove")} +
+ + + {(m, i) => ( +
+
+ setModel(i(), "id", v)} + validationState={m.err.id ? "invalid" : undefined} + error={m.err.id} />
- )} - - -
- -
- - - {(h, i) => ( -
-
- setHeader(i(), "key", v)} - validationState={h.err.key ? "invalid" : undefined} - error={h.err.key} - /> -
-
- setHeader(i(), "value", v)} - validationState={h.err.value ? "invalid" : undefined} - error={h.err.value} - /> -
- removeHeader(i())} - disabled={form.headers.length <= 1} - aria-label={language.t("provider.custom.headers.remove")} +
+ setModel(i(), "name", v)} + validationState={m.err.name ? "invalid" : undefined} + error={m.err.name} />
- )} - - -
+ removeModel(i())} + disabled={form.models.length <= 1} + aria-label={language.t("provider.custom.models.remove")} + /> +
+ )} +
+ +
- - -
-
+
+ + + +
) } diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx index a0821ef1a36f..d6c5e9918670 100644 --- a/packages/app/src/components/dialog-manage-models.tsx +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -3,13 +3,26 @@ import { List } from "@opencode-ai/ui/list" import { Switch } from "@opencode-ai/ui/switch" import { Tooltip } from "@opencode-ai/ui/tooltip" import { Button } from "@opencode-ai/ui/button" -import type { Component } from "solid-js" +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" +import { Dialog as DialogV2, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/v2/dialog-v2" +import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" +import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" +import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { Switch as SwitchV2 } from "@opencode-ai/ui/v2/switch-v2" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { useFilteredList } from "@opencode-ai/ui/hooks" +import { For, Show, type Component } from "solid-js" import { useLocal } from "@/context/local" import { popularProviders } from "@/hooks/use-providers" import { useLanguage } from "@/context/language" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { DialogSelectProvider } from "./dialog-select-provider" +import { DialogConnectProvider } from "./dialog-connect-provider" import { decode64 } from "@/utils/base64" +import { SettingsListV2 } from "./settings-v2/parts/list" +import { SettingsRowV2 } from "./settings-v2/parts/row" +import "./settings-v2/settings-v2.css" + +type ModelItem = ReturnType["model"]["list"]>[number] export const DialogManageModels: Component = () => { const local = useLocal() @@ -18,7 +31,7 @@ export const DialogManageModels: Component = () => { const directory = () => decode64(local.slug()) const handleConnectProvider = () => { - dialog.show(() => ) + void dialog.show(() => ) } const providerRank = (id: string) => popularProviders.indexOf(id) const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID) @@ -102,3 +115,151 @@ export const DialogManageModels: Component = () => { ) } + +export const DialogManageModelsV2: Component = () => { + const local = useLocal() + const language = useLanguage() + const dialog = useDialog() + const directory = () => decode64(local.slug()) + + const handleConnectProvider = () => { + void dialog.show(() => ) + } + const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID) + const providerVisible = (providerID: string) => + providerList(providerID).every((x) => local.model.visible({ modelID: x.id, providerID: x.provider.id })) + const setProviderVisibility = (providerID: string, checked: boolean) => { + providerList(providerID).forEach((x) => { + local.model.setVisibility({ modelID: x.id, providerID: x.provider.id }, checked) + }) + } + const setModelVisibility = (item: ModelItem, checked: boolean) => { + local.model.setVisibility({ modelID: item.id, providerID: item.provider.id }, checked) + } + const list = useFilteredList({ + items: () => local.model.list(), + key: (x) => `${x.provider.id}:${x.id}`, + filterKeys: ["provider.name", "name", "id"], + sortBy: (a, b) => a.name.localeCompare(b.name), + groupBy: (x) => x.provider.id, + sortGroupsBy: (a, b) => { + const aRank = popularProviders.indexOf(a.category) + const bRank = popularProviders.indexOf(b.category) + const aPopular = aRank >= 0 + const bPopular = bRank >= 0 + if (aPopular && !bPopular) return -1 + if (!aPopular && bPopular) return 1 + return aRank - bRank + }, + }) + + return ( + + + + + {language.t("command.provider.connect")} + + + +
+
+ list.onInput(event.currentTarget.value)} + placeholder={language.t("dialog.model.search.placeholder")} + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + autofocus + aria-label={language.t("dialog.model.search.placeholder")} + /> + + } + onClick={() => list.clear()} + aria-label={language.t("common.clear")} + /> + +
+
+
+
+ + {language.t("common.loading")} + {language.t("common.loading.ellipsis")} +
+ } + > + 0} + fallback={ +
+ {language.t("dialog.model.empty")} + + "{list.filter()}" + +
+ } + > + + {(group) => ( +
+
+
+ +

{group.items[0].provider.name}

+
+
+ setProviderVisibility(group.category, checked)} + hideLabel + > + {group.items[0].provider.name} + +
+
+ + + {(item) => ( + +
+ setModelVisibility(item, checked)} + hideLabel + > + {item.name} + +
+
+ )} +
+
+
+ )} +
+
+ +
+
+ + + ) +} diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 49a0d5961aa6..905abe1e2dd3 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -1,329 +1,81 @@ -import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" import { Keybind } from "@opencode-ai/ui/keybind" import { List } from "@opencode-ai/ui/list" -import { base64Encode } from "@opencode-ai/core/util/encode" import { getDirectory, getFilename } from "@opencode-ai/core/util/path" -import { useNavigate } from "@solidjs/router" -import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js" -import { formatKeybind, useCommand, type CommandOption } from "@/context/command" -import { useServerSDK, type ServerSDK } from "@/context/server-sdk" -import { useServerSync } from "@/context/server-sync" -import { useLayout } from "@/context/layout" -import { useFile } from "@/context/file" +import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js" +import { formatKeybind } from "@/context/command" +import { useServerSDK } from "@/context/server-sdk" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" import { useSessionLayout } from "@/pages/session/session-layout" -import { createSessionTabs } from "@/pages/session/helpers" import { decode64 } from "@/utils/base64" import { getRelativeTime } from "@/utils/time" +import { + createCommandPaletteFileEntry, + createCommandPaletteFileOpener, + createCommandPaletteModel, + uniqueCommandPaletteEntries, + type CommandPaletteEntry, +} from "./command-palette" +import { DialogCommandPaletteV2 } from "./dialog-command-palette-v2" const DialogSelectFileV2 = lazy(() => import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })), ) - -type EntryType = "command" | "file" | "session" - -type Entry = { - id: string - type: EntryType - title: string - description?: string - keybind?: string - category: string - option?: CommandOption - path?: string - directory?: string - sessionID?: string - archived?: number - updated?: number -} - type DialogSelectFileMode = "all" | "files" -const ENTRY_LIMIT = 5 -const COMMON_COMMAND_IDS = [ - "session.new", - "workspace.new", - "session.previous", - "session.next", - "terminal.toggle", - "review.toggle", -] as const - -const uniqueEntries = (items: Entry[]) => { - const seen = new Set() - const out: Entry[] = [] - for (const item of items) { - if (seen.has(item.id)) continue - seen.add(item.id) - out.push(item) - } - return out -} - -const createCommandEntry = (option: CommandOption, category: string): Entry => ({ - id: "command:" + option.id, - type: "command", - title: option.title, - description: option.description, - keybind: option.keybind, - category, - option, -}) - -const createFileEntry = (path: string, category: string): Entry => ({ - id: "file:" + path, - type: "file", - title: path, - category, - path, -}) - -const createSessionEntry = ( - input: { - directory: string - id: string - title: string - description: string - archived?: number - updated?: number - }, - category: string, -): Entry => ({ - id: `session:${input.directory}:${input.id}`, - type: "session", - title: input.title, - description: input.description, - category, - directory: input.directory, - sessionID: input.id, - archived: input.archived, - updated: input.updated, -}) - -function createCommandEntries(props: { - filesOnly: () => boolean - command: ReturnType - language: ReturnType -}) { - const allowed = createMemo(() => { - if (props.filesOnly()) return [] - return props.command.options.filter( - (option) => - !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open", - ) - }) - - const list = createMemo(() => { - const category = props.language.t("palette.group.commands") - return allowed().map((option) => createCommandEntry(option, category)) - }) - - const picks = createMemo(() => { - const all = allowed() - const order = new Map(COMMON_COMMAND_IDS.map((id, index) => [id, index])) - const picked = all.filter((option) => order.has(option.id)) - const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT) - const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base - const category = props.language.t("palette.group.commands") - return sorted.map((option) => createCommandEntry(option, category)) - }) - - return { allowed, list, picks } -} - -function createFileEntries(props: { - file: ReturnType - tabs: () => ReturnType["tabs"]> - language: ReturnType -}) { - const tabState = createSessionTabs({ - tabs: props.tabs, - pathFromTab: props.file.pathFromTab, - normalizeTab: (tab) => (tab.startsWith("file://") ? props.file.tab(tab) : tab), - }) - const recent = createMemo(() => { - const all = tabState.openedTabs() - const active = tabState.activeFileTab() - const order = active ? [active, ...all.filter((item) => item !== active)] : all - const seen = new Set() - const category = props.language.t("palette.group.files") - const items: Entry[] = [] - - for (const item of order) { - const path = props.file.pathFromTab(item) - if (!path) continue - if (seen.has(path)) continue - seen.add(path) - items.push(createFileEntry(path, category)) - } - - return items.slice(0, ENTRY_LIMIT) - }) - - const root = createMemo(() => { - const category = props.language.t("palette.group.files") - const nodes = props.file.tree.children("") - const paths = nodes - .filter((node) => node.type === "file") - .map((node) => node.path) - .sort((a, b) => a.localeCompare(b)) - return paths.slice(0, ENTRY_LIMIT).map((path) => createFileEntry(path, category)) - }) - - return { recent, root } -} +export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { + const platform = usePlatform() + const settings = useSettings() + const filesOnly = () => props.mode === "files" -function createSessionEntries(props: { - workspaces: () => string[] - label: (directory: string) => string - serverSDK: ServerSDK - language: ReturnType -}) { - const state: { - token: number - inflight: Promise | undefined - cached: Entry[] | undefined - } = { - token: 0, - inflight: undefined, - cached: undefined, + if (!filesOnly() && settings.general.newLayoutDesigns()) { + return } - const sessions = (text: string) => { - const query = text.trim() - if (!query) { - state.token += 1 - state.inflight = undefined - state.cached = undefined - return [] as Entry[] - } - - if (state.cached) return state.cached - if (state.inflight) return state.inflight - - const current = state.token - const dirs = props.workspaces() - if (dirs.length === 0) return [] as Entry[] - - state.inflight = Promise.all( - dirs.map((directory) => { - const description = props.label(directory) - return props.serverSDK.client.session - .list({ directory, roots: true }) - .then((x) => - (x.data ?? []) - .filter((s) => !!s?.id) - .map((s) => ({ - id: s.id, - title: s.title ?? props.language.t("command.session.new"), - description, - directory, - archived: s.time?.archived, - updated: s.time?.updated, - })), - ) - .catch( - () => - [] as { - id: string - title: string - description: string - directory: string - archived?: number - updated?: number - }[], - ) - }), - ) - .then((results) => { - if (state.token !== current) return [] as Entry[] - const seen = new Set() - const category = props.language.t("command.category.session") - const next = results - .flat() - .filter((item) => { - const key = `${item.directory}:${item.id}` - if (seen.has(key)) return false - seen.add(key) - return true - }) - .map((item) => createSessionEntry(item, category)) - state.cached = next - return next - }) - .catch(() => [] as Entry[]) - .finally(() => { - state.inflight = undefined - }) - - return state.inflight + if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) { + return } - return { sessions } + return } -export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { - const command = useCommand() +function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) { const language = useLanguage() - const platform = usePlatform() - const settings = useSettings() - const layout = useLayout() - const file = useFile() - const dialog = useDialog() - const navigate = useNavigate() const serverSDK = useServerSDK() - const serverSync = useServerSync() - const { params, tabs, view } = useSessionLayout() - const filesOnly = () => props.mode === "files" - const state = { cleanup: undefined as (() => void) | void, committed: false } - const [grouped, setGrouped] = createSignal(false) - const commandEntries = createCommandEntries({ filesOnly, command, language }) - const fileEntries = createFileEntries({ file, tabs, language }) - + const { params } = useSessionLayout() const projectDirectory = createMemo(() => decode64(params.dir) ?? "") - const project = createMemo(() => { - const directory = projectDirectory() - if (!directory) return - return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory)) - }) - const workspaces = createMemo(() => { - const directory = projectDirectory() - const current = project() - if (!current) return directory ? [directory] : [] + const openFile = createCommandPaletteFileOpener(props.onOpenFile) - const dirs = [current.worktree, ...(current.sandboxes ?? [])] - if (directory && !dirs.includes(directory)) return [...dirs, directory] - return dirs - }) - const homedir = createMemo(() => serverSync().data.path.home) - const label = (directory: string) => { - const current = project() - const kind = - current && directory === current.worktree - ? language.t("workspace.type.local") - : language.t("workspace.type.sandbox") - const [store] = serverSync().child(directory, { bootstrap: false }) - const home = homedir() - const path = home ? directory.replace(home, "~") : directory - const name = store.vcs?.branch ?? getFilename(directory) - return `${kind} : ${name || path}` - } + return ( + { + if (typeof result !== "string") return + openFile(result) + }} + /> + ) +} - const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language }) +function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) { + const palette = createCommandPaletteModel(props) + const [grouped, setGrouped] = createSignal(false) const items = async (text: string) => { const query = text.trim() setGrouped(query.length > 0) - if (!query && filesOnly()) { - const loaded = file.tree.state("")?.loaded - const pending = loaded ? Promise.resolve() : file.tree.list("") - const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()]) + if (!query && props.filesOnly()) { + const loaded = palette.file.tree.state("")?.loaded + const pending = loaded ? Promise.resolve() : palette.file.tree.list("") + const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()]) if (loaded || next.length > 0) { void pending @@ -331,79 +83,24 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil } await pending - return uniqueEntries([...fileEntries.recent(), ...fileEntries.root()]) - } - - if (!query) return [...commandEntries.picks(), ...fileEntries.recent()] - - if (filesOnly()) { - const files = await file.searchFiles(query) - const category = language.t("palette.group.files") - return files.map((path) => createFileEntry(path, category)) + return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()]) } - const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))]) - const category = language.t("palette.group.files") - const entries = files.map((path) => createFileEntry(path, category)) - return [...commandEntries.list(), ...nextSessions, ...entries] - } - - const handleMove = (item: Entry | undefined) => { - state.cleanup?.() - if (!item) return - if (item.type !== "command") return - state.cleanup = item.option?.onHighlight?.() - } - - const open = (path: string) => { - const value = file.tab(path) - void tabs().open(value) - void file.load(path) - if (!view().reviewPanel.opened()) view().reviewPanel.open() - layout.fileTree.setTab("all") - props.onOpenFile?.(path) - tabs().setActive(value) - } - - const handleSelect = (item: Entry | undefined) => { - if (!item) return - state.committed = true - state.cleanup = undefined - dialog.close() + if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()] - if (item.type === "command") { - item.option?.onSelect?.("palette") - return + if (props.filesOnly()) { + const files = await palette.file.searchFiles(query) + const category = palette.language.t("palette.group.files") + return files.map((path) => createCommandPaletteFileEntry(path, category)) } - if (item.type === "session") { - if (!item.directory || !item.sessionID) return - navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`) - return - } - - if (!item.path) return - open(item.path) - } - - onCleanup(() => { - if (state.committed) return - state.cleanup?.() - }) - - if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) { - return ( - { - if (typeof result !== "string") return - open(result) - }} - /> - ) + const [files, nextSessions] = await Promise.all([ + palette.file.searchFiles(query), + Promise.resolve(palette.sessions(query)), + ]) + const category = palette.language.t("palette.group.files") + const entries = files.map((path) => createCommandPaletteFileEntry(path, category)) + return [...palette.commandEntries(), ...nextSessions, ...entries] } return ( @@ -411,21 +108,21 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil item.id} filterKeys={["title", "description", "category"]} skipFilter={(item) => item.type === "file"} groupBy={grouped() ? (item) => item.category : () => ""} - onMove={handleMove} - onSelect={handleSelect} + onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)} + onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)} > {(item) => (
- {formatKeybind(item.keybind ?? "", language.t)} + {formatKeybind(item.keybind ?? "", palette.language.t)}
@@ -479,7 +176,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
- {getRelativeTime(new Date(item.updated!).toISOString(), language.t)} + {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
diff --git a/packages/app/src/components/dialog-select-model-search.test.ts b/packages/app/src/components/dialog-select-model-search.test.ts new file mode 100644 index 000000000000..3ee800914042 --- /dev/null +++ b/packages/app/src/components/dialog-select-model-search.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test" +import { matchesModelSearch } from "./dialog-select-model-search" + +describe("matchesModelSearch", () => { + test("matches model names across separators", () => { + expect(matchesModelSearch("gpt 5", ["GPT-5.5"])).toBe(true) + expect(matchesModelSearch("gpt-5", ["GPT-5.5"])).toBe(true) + expect(matchesModelSearch("gpt5", ["GPT-5.5"])).toBe(true) + }) + + test("matches any searchable model field", () => { + expect(matchesModelSearch("open ai", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(true) + expect(matchesModelSearch("gpt 5", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(true) + }) + + test("does not match unrelated searches", () => { + expect(matchesModelSearch("claude", ["GPT-5.5", "gpt-5.5", "OpenAI"])).toBe(false) + }) +}) diff --git a/packages/app/src/components/dialog-select-model-search.ts b/packages/app/src/components/dialog-select-model-search.ts new file mode 100644 index 000000000000..901f3fc114a6 --- /dev/null +++ b/packages/app/src/components/dialog-select-model-search.ts @@ -0,0 +1,18 @@ +export const normalizeModelSearch = (value: string) => + value + .toLowerCase() + .replace(/[^\p{Letter}\p{Number}]+/gu, " ") + .trim() + .replace(/\s+/g, " ") + +export const compactModelSearch = (value: string) => normalizeModelSearch(value).replaceAll(" ", "") + +export const matchesModelSearch = (query: string, values: string[]) => { + const search = normalizeModelSearch(query) + if (!search) return true + + const compactSearch = compactModelSearch(query) + return values.some( + (value) => normalizeModelSearch(value).includes(search) || compactModelSearch(value).includes(compactSearch), + ) +} diff --git a/packages/app/src/components/dialog-select-model-unpaid-v2.tsx b/packages/app/src/components/dialog-select-model-unpaid-v2.tsx new file mode 100644 index 000000000000..0ce9f18c1698 --- /dev/null +++ b/packages/app/src/components/dialog-select-model-unpaid-v2.tsx @@ -0,0 +1,173 @@ +import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2" +import { Icon } from "@opencode-ai/ui/v2/icon" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { ScrollView } from "@opencode-ai/ui/scroll-view" +import { Tag } from "@opencode-ai/ui/v2/badge-v2" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js" +import { useLocal } from "@/context/local" +import { popularProviders, useProviders } from "@/hooks/use-providers" +import { decode64 } from "@/utils/base64" +import { useLanguage } from "@/context/language" +import { ModelTooltip } from "./model-tooltip" + +type ModelState = ReturnType["model"] + +export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (props) => { + const local = useLocal() + const model = props.model ?? local.model + const dialog = useDialog() + const directory = () => decode64(local.slug()) + const providers = useProviders(directory) + const language = useLanguage() + const modelKey = (item: ReturnType[number]) => `${item.provider.id}:${item.id}` + const currentKey = createMemo(() => { + const c = model.current() + return c ? `${c.provider.id}:${c.id}` : undefined + }) + const isFree = (item: ReturnType[number]) => + item.provider.id === "opencode" && (!item.cost || item.cost.input === 0) + + const openProviders = (provider?: string) => { + void import("./dialog-connect-provider").then((x) => { + const controller = x.useProviderConnectController() + controller.select(provider) + void dialog.show(() => ) + }) + } + + const selectModel = (item: ReturnType[number]) => { + model.set({ modelID: item.id, providerID: item.provider.id }, { recent: true }) + dialog.close() + } + + // Focus starts on the dialog's close button, outside the list, so listen at the + // document level while the dialog is mounted instead of on the list container. + let listEl: HTMLDivElement | undefined + onMount(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return + if (!listEl) return + const buttons = Array.from(listEl.querySelectorAll("button")) + if (buttons.length === 0) return + const index = buttons.indexOf(document.activeElement as HTMLButtonElement) + const next = + index < 0 ? (e.key === "ArrowDown" ? 0 : buttons.length - 1) : index + (e.key === "ArrowDown" ? 1 : -1) + buttons[(next + buttons.length) % buttons.length]?.focus() + e.preventDefault() + } + document.addEventListener("keydown", handleKeyDown) + onCleanup(() => document.removeEventListener("keydown", handleKeyDown)) + }) + + return ( + + + {language.t("dialog.model.select.title")} + +
+ + +
+
+
+
+ {language.t("dialog.model.unpaid.freeModels.title")} +
+
+ + {(item) => ( + } + > + + + )} + +
+ +
+
+
+
+ {language.t("dialog.model.unpaid.addMore.title")} +
+
+
+ { + if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) { + return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) + } + return a.name.localeCompare(b.name) + })} + > + {(provider) => ( + + )} + + +
+
+
+
+
+
+ + ) +} diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx index bcb4a2fbcbf4..4611a36c950e 100644 --- a/packages/app/src/components/dialog-select-model-unpaid.tsx +++ b/packages/app/src/components/dialog-select-model-unpaid.tsx @@ -22,17 +22,16 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props const providers = useProviders(directory) const language = useLanguage() - const connect = (provider: string) => { + const openProviders = (provider?: string) => { void import("./dialog-connect-provider").then((x) => { - dialog.show(() => ) + const controller = x.useProviderConnectController() + controller.select(provider) + void dialog.show(() => ) }) } - const all = () => { - void import("./dialog-select-provider").then((x) => { - dialog.show(() => ) - }) - } + const connect = (provider: string) => openProviders(provider) + const all = () => openProviders() let listRef: ListRef | undefined const handleKeyDown = (e: KeyboardEvent) => { diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index e16d9a665716..4fb7891ec950 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -1,23 +1,57 @@ import { Popover as Kobalte } from "@kobalte/core/popover" -import { Component, ComponentProps, createMemo, JSX, Show, ValidComponent } from "solid-js" +import { + Component, + ComponentProps, + createEffect, + createMemo, + For, + JSX, + onCleanup, + Show, + ValidComponent, +} from "solid-js" import { createStore } from "solid-js/store" import { useLocal } from "@/context/local" import { useDialog } from "@opencode-ai/ui/context/dialog" import { popularProviders } from "@/hooks/use-providers" import { Button } from "@opencode-ai/ui/button" import { IconButton } from "@opencode-ai/ui/icon-button" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { Tag } from "@opencode-ai/ui/tag" import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { Tooltip } from "@opencode-ai/ui/tooltip" +import { Icon } from "@opencode-ai/ui/v2/icon" +import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2" +import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { ModelTooltip } from "./model-tooltip" import { useLanguage } from "@/context/language" import { decode64 } from "@/utils/base64" +import { handleDocumentSearchKeydown } from "@/utils/search-keydown" +import { createEventListener } from "@solid-primitives/event-listener" +import { matchesModelSearch } from "./dialog-select-model-search" const isFree = (provider: string, cost: { input: number } | undefined) => provider === "opencode" && (!cost || cost.input === 0) type ModelState = ReturnType["model"] +type ModelItem = ReturnType[number] + +const modelKey = (model: ModelItem) => `${model.provider.id}:${model.id}` +const manageKey = "action:manage" + +const sortModelGroups = (a: { category: string; items: ModelItem[] }, b: { category: string; items: ModelItem[] }) => { + const aIndex = popularProviders.indexOf(a.category) + const bIndex = popularProviders.indexOf(b.category) + const aPopular = aIndex >= 0 + const bPopular = bIndex >= 0 + + if (aPopular && !bPopular) return -1 + if (!aPopular && bPopular) return 1 + if (aPopular && bPopular) return aIndex - bIndex + return a.items[0].provider.name.localeCompare(b.items[0].provider.name) +} const ModelList: Component<{ provider?: string @@ -59,6 +93,7 @@ const ModelList: Component<{ class="w-full" placement="right-start" gutter={12} + openDelay={0} value={} > {node} @@ -122,8 +157,8 @@ export function ModelSelectorPopover(props: { const handleConnectProvider = () => { close("provider") - void import("./dialog-select-provider").then((x) => { - dialog.show(() => ) + void import("./dialog-connect-provider").then((x) => { + void dialog.show(() => ) }) } const language = useLanguage() @@ -199,6 +234,285 @@ export function ModelSelectorPopover(props: { ) } +export function ModelSelectorPopoverV2(props: { + provider?: string + model?: ModelState + children?: JSX.Element + triggerAs?: ValidComponent + triggerProps?: ModelSelectorTriggerProps + onClose?: () => void +}) { + const model = props.model ?? useLocal().model + const language = useLanguage() + const dialog = useDialog() + const [store, setStore] = createStore({ open: false, search: "", active: "" }) + let searchRef: HTMLInputElement | undefined + let contentRef: HTMLDivElement | undefined + let restoreTrigger = true + + const allModels = createMemo(() => + model + .list() + .filter((item) => model.visible({ modelID: item.id, providerID: item.provider.id })) + .filter((item) => (props.provider ? item.provider.id === props.provider : true)), + ) + const models = createMemo(() => { + const search = store.search.trim() + const filtered = search + ? allModels().filter((item) => matchesModelSearch(search, [item.name, item.id, item.provider.name])) + : allModels() + + return [...filtered].sort((a, b) => a.name.localeCompare(b.name)) + }) + const groups = createMemo(() => { + const byProvider = new Map() + for (const item of models()) { + byProvider.set(item.provider.id, [...(byProvider.get(item.provider.id) ?? []), item]) + } + return Array.from(byProvider, ([category, items]) => ({ category, items })).sort(sortModelGroups) + }) + const keys = () => [...models().map(modelKey), manageKey] + const current = () => { + const value = model.current() + return value ? `${value.provider.id}:${value.id}` : undefined + } + const initialActive = () => { + const selected = current() + const options = keys() + if (selected && options.includes(selected)) return selected + return options[0] ?? "" + } + const activeItem = () => + store.active ? contentRef?.querySelector(`[data-option-key="${CSS.escape(store.active)}"]`) : undefined + const afterClose = (callback: () => void) => { + const complete = () => { + if (contentRef?.isConnected) { + requestAnimationFrame(complete) + return + } + requestAnimationFrame(() => requestAnimationFrame(callback)) + } + requestAnimationFrame(complete) + } + const setOpen = (open: boolean) => { + if (open) { + restoreTrigger = true + setStore({ open: true, active: initialActive() }) + setTimeout(() => + requestAnimationFrame(() => { + searchRef?.focus() + activeItem()?.scrollIntoView({ block: "nearest" }) + }), + ) + return + } + setStore({ open: false, search: "", active: "" }) + } + const select = (item: ModelItem) => { + model.set({ modelID: item.id, providerID: item.provider.id }, { recent: true }) + props.onClose?.() + } + const selectModel = (item: ModelItem) => { + restoreTrigger = false + setOpen(false) + afterClose(() => select(item)) + } + const manage = () => { + restoreTrigger = false + setOpen(false) + afterClose(() => { + void import("./dialog-manage-models").then((x) => { + dialog.show(() => ) + }) + }) + } + const selectActive = () => { + const item = models().find((item) => modelKey(item) === store.active) + if (item) { + selectModel(item) + return + } + if (store.active === manageKey) manage() + } + const moveActive = (delta: number) => { + const options = keys() + if (options.length === 0) return + const index = options.indexOf(store.active) + const start = index === -1 ? 0 : index + setStore("active", options[(start + delta + options.length) % options.length]) + queueMicrotask(() => activeItem()?.scrollIntoView({ block: "nearest" })) + } + const setSearch = (value: string) => { + const search = value.trim() + const first = [...allModels()] + .sort((a, b) => a.name.localeCompare(b.name)) + .find((item) => matchesModelSearch(search, [item.name, item.id, item.provider.name])) + setStore({ search: value, active: first ? modelKey(first) : manageKey }) + } + + createEffect(() => { + if (!store.open) return + createEventListener( + document, + "keydown", + (event: KeyboardEvent) => handleDocumentSearchKeydown(searchRef, event, store.search, setSearch), + true, + ) + }) + + return ( + + + {props.children} + + + (contentRef = el)} + class="w-[284px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none" + onPointerDownOutside={() => (restoreTrigger = false)} + onFocusOutside={() => (restoreTrigger = false)} + onCloseAutoFocus={(event) => { + if (!restoreTrigger) event.preventDefault() + }} + > +
+
+ + (searchRef = el)} + value={store.search} + placeholder={language.t("dialog.model.search.placeholder")} + class="h-7 min-w-0 flex-1 border-0 bg-transparent text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base outline-none placeholder:text-v2-text-text-faint" + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + onInput={(event) => setSearch(event.currentTarget.value)} + onKeyDown={(event) => { + if (event.key === "Tab") return + event.stopPropagation() + if (event.key === "Escape") { + event.preventDefault() + restoreTrigger = false + setOpen(false) + afterClose(() => props.onClose?.()) + return + } + if (event.altKey || event.metaKey) return + if (event.key === "ArrowDown") { + event.preventDefault() + moveActive(1) + return + } + if (event.key === "ArrowUp") { + event.preventDefault() + moveActive(-1) + return + } + if (event.key === "Enter" && !event.isComposing) { + event.preventDefault() + selectActive() + } + }} + /> + + + +
+
+
+ +
+ 0} + fallback={ +
+ {language.t("dialog.model.empty")} +
+ } + > + + {(group) => ( + + + {group.items[0].provider.name} + + + + {(item) => ( + + } + > + { + setStore("active", modelKey(item)) + setTimeout(() => searchRef?.focus()) + }} + onSelect={() => selectModel(item)} + > + {item.name} + + {language.t("model.tag.free")} + + + {language.t("model.tag.latest")} + + + + )} + + + + )} + +
+
+
+
+
+ { + setStore("active", manageKey) + setTimeout(() => searchRef?.focus()) + }} + onSelect={manage} + > + + {language.t("dialog.model.manage")} + +
+ + + + ) +} + export const DialogSelectModel: Component<{ provider?: string; model?: ModelState }> = (props) => { const dialog = useDialog() const language = useLanguage() @@ -206,8 +520,8 @@ export const DialogSelectModel: Component<{ provider?: string; model?: ModelStat const directory = () => decode64(local.slug()) const provider = () => { - void import("./dialog-select-provider").then((x) => { - dialog.show(() => ) + void import("./dialog-connect-provider").then((x) => { + void dialog.show(() => ) }) } diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx deleted file mode 100644 index ff0ab5d2bf0c..000000000000 --- a/packages/app/src/components/dialog-select-provider.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { type Accessor, Component, Show } from "solid-js" -import { useDialog } from "@opencode-ai/ui/context/dialog" -import { popularProviders, useProviders } from "@/hooks/use-providers" -import { Dialog } from "@opencode-ai/ui/dialog" -import { List } from "@opencode-ai/ui/list" -import { Tag } from "@opencode-ai/ui/tag" -import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import { DialogConnectProvider } from "./dialog-connect-provider" -import { useLanguage } from "@/context/language" -import { DialogCustomProvider } from "./dialog-custom-provider" - -const CUSTOM_ID = "_custom" - -export const DialogSelectProvider: Component<{ directory?: Accessor }> = (props) => { - const dialog = useDialog() - const providers = useProviders(props.directory) - const language = useLanguage() - - const popularGroup = () => language.t("dialog.provider.group.popular") - const otherGroup = () => language.t("dialog.provider.group.other") - const customLabel = () => language.t("settings.providers.tag.custom") - const note = (id: string) => { - if (id === "anthropic") return language.t("dialog.provider.anthropic.note") - if (id === "openai") return language.t("dialog.provider.openai.note") - if (id.startsWith("github-copilot")) return language.t("dialog.provider.copilot.note") - if (id === "opencode-go") return language.t("dialog.provider.opencodeGo.tagline") - } - - return ( - - x?.id} - items={() => { - language.locale() - return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()] - }} - filterKeys={["id", "name"]} - groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())} - sortBy={(a, b) => { - if (a.id === CUSTOM_ID) return -1 - if (b.id === CUSTOM_ID) return 1 - if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) - return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) - return a.name.localeCompare(b.name) - }} - sortGroupsBy={(a, b) => { - const popular = popularGroup() - if (a.category === popular && b.category !== popular) return -1 - if (b.category === popular && a.category !== popular) return 1 - return 0 - }} - onSelect={(x) => { - if (!x) return - if (x.id === CUSTOM_ID) { - dialog.show(() => ) - return - } - dialog.show(() => ) - }} - > - {(i) => ( -
- - {i.name} - -
{language.t("dialog.provider.opencode.tagline")}
-
- - {language.t("settings.providers.tag.custom")} - - - {language.t("dialog.provider.tag.recommended")} - - {(value) =>
{value()}
}
- - {language.t("dialog.provider.tag.recommended")} - -
- )} -
-
- ) -} diff --git a/packages/app/src/components/dialog-settings.tsx b/packages/app/src/components/dialog-settings.tsx index 20d71f4bfd44..b554fa79a822 100644 --- a/packages/app/src/components/dialog-settings.tsx +++ b/packages/app/src/components/dialog-settings.tsx @@ -1,22 +1,35 @@ -import { Component } from "solid-js" +import { Component, createSignal, startTransition } from "solid-js" import { Dialog } from "@opencode-ai/ui/dialog" import { Tabs } from "@opencode-ai/ui/tabs" import { Icon } from "@opencode-ai/ui/icon" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" +import { useDialog } from "@opencode-ai/ui/context/dialog" import { SettingsGeneral } from "./settings-general" import { SettingsKeybinds } from "./settings-keybinds" import { SettingsProviders } from "./settings-providers" import { SettingsModels } from "./settings-models" import { SettingsServers } from "./settings-servers" -export const DialogSettings: Component = () => { +export const DialogSettings: Component<{ defaultValue?: string }> = (props) => { const language = useLanguage() const platform = usePlatform() + const dialog = useDialog() + const [tab, setTab] = createSignal(props.defaultValue ?? "general") + + const showProviders = () => { + void dialog.show(() => ) + } return ( - + void startTransition(() => setTab(value))} + class="h-full settings-dialog" + >
@@ -70,7 +83,7 @@ export const DialogSettings: Component = () => { - + diff --git a/packages/app/src/components/file-tree-v2-model.test.ts b/packages/app/src/components/file-tree-v2-model.test.ts new file mode 100644 index 000000000000..288f7112e929 --- /dev/null +++ b/packages/app/src/components/file-tree-v2-model.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from "bun:test" +import { buildFileTreeV2Model, flattenFileTreeV2 } from "./file-tree-v2-model" + +describe("file tree v2 model", () => { + test("builds sorted depth-first rows", () => { + const model = buildFileTreeV2Model(["src/z.ts", "src/lib/b.ts", "src/lib/a.ts", "README.md", "docs/guide.md"]) + + expect(model.total).toBe(8) + expect(flattenFileTreeV2(model, () => true).map((row) => [row.node.path, row.node.type, row.level])).toEqual([ + ["docs", "directory", 0], + ["docs/guide.md", "file", 1], + ["src", "directory", 0], + ["src/lib", "directory", 1], + ["src/lib/a.ts", "file", 2], + ["src/lib/b.ts", "file", 2], + ["src/z.ts", "file", 1], + ["README.md", "file", 0], + ]) + }) + + test("omits descendants of collapsed directories", () => { + const model = buildFileTreeV2Model(["src/lib/a.ts", "src/z.ts"]) + + expect(flattenFileTreeV2(model, (path) => path !== "src/lib").map((row) => row.node.path)).toEqual([ + "src", + "src/lib", + "src/z.ts", + ]) + }) + + test("normalizes separators and duplicate paths", () => { + const model = buildFileTreeV2Model(["src\\lib\\a.ts", "src/lib/a.ts", "/src//lib/b.ts/"]) + const rows = flattenFileTreeV2(model, () => true) + + expect(model.total).toBe(4) + expect(rows.map((row) => row.node.path)).toEqual(["src", "src/lib", "src/lib/a.ts", "src/lib/b.ts"]) + expect(rows.find((row) => row.node.path === "src/lib/a.ts")?.node.originalPath).toBe("src\\lib\\a.ts") + }) + + test("supports paths deeper than the legacy recursion limit", () => { + const file = `${Array.from({ length: 130 }, (_, index) => `dir-${index}`).join("/")}/file.ts` + const model = buildFileTreeV2Model([file]) + + expect(flattenFileTreeV2(model, () => true)).toHaveLength(131) + }) +}) diff --git a/packages/app/src/components/file-tree-v2-model.ts b/packages/app/src/components/file-tree-v2-model.ts new file mode 100644 index 000000000000..2127b6800f86 --- /dev/null +++ b/packages/app/src/components/file-tree-v2-model.ts @@ -0,0 +1,77 @@ +import type { FileNode } from "@opencode-ai/sdk/v2" + +export type FileTreeV2Model = { + children: ReadonlyMap + total: number +} + +export type FileTreeV2Node = FileNode & { originalPath: string } + +export type FileTreeV2Row = { + node: FileTreeV2Node + level: number +} + +export function normalizeFileTreeV2Path(value: string) { + return value + .replaceAll("\\", "/") + .replace(/^\/+|\/+$/g, "") + .replace(/\/{2,}/g, "/") +} + +export function buildFileTreeV2Model(paths: readonly string[]): FileTreeV2Model { + const nodes = new Map() + + paths.forEach((value) => { + const file = normalizeFileTreeV2Path(value) + if (!file) return + + const parts = file.split("/") + parts.forEach((name, index) => { + const path = parts.slice(0, index + 1).join("/") + if (nodes.has(path)) return + nodes.set(path, { + name, + path, + absolute: path, + type: index === parts.length - 1 ? "file" : "directory", + ignored: false, + originalPath: index === parts.length - 1 ? value : path, + }) + }) + }) + + const children = new Map() + nodes.forEach((node) => { + const index = node.path.lastIndexOf("/") + const parent = index === -1 ? "" : node.path.slice(0, index) + const list = children.get(parent) + if (list) list.push(node) + else children.set(parent, [node]) + }) + children.forEach((nodes) => + nodes.sort((a, b) => { + if (a.type !== b.type) return a.type === "directory" ? -1 : 1 + return a.name.localeCompare(b.name) + }), + ) + + return { children, total: nodes.size } +} + +export function flattenFileTreeV2(model: FileTreeV2Model, expanded: (path: string) => boolean) { + const rows: FileTreeV2Row[] = [] + const stack = (model.children.get("") ?? []).toReversed().map((node) => ({ node, level: 0 })) + + while (stack.length > 0) { + const row = stack.pop()! + rows.push(row) + if (row.node.type !== "directory" || !expanded(row.node.path)) continue + const children = model.children.get(row.node.path) ?? [] + for (let index = children.length - 1; index >= 0; index--) { + stack.push({ node: children[index]!, level: row.level + 1 }) + } + } + + return rows +} diff --git a/packages/app/src/components/file-tree-v2.tsx b/packages/app/src/components/file-tree-v2.tsx new file mode 100644 index 000000000000..c712a8044360 --- /dev/null +++ b/packages/app/src/components/file-tree-v2.tsx @@ -0,0 +1,268 @@ +import { useFile } from "@/context/file" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import "@opencode-ai/ui/v2/file-tree-v2.css" +import { + createEffect, + createMemo, + createSignal, + For, + Show, + splitProps, + type ComponentProps, + type ParentProps, +} from "solid-js" +import { Dynamic } from "solid-js/web" +import type { FileNode } from "@opencode-ai/sdk/v2" +import { Icon } from "@opencode-ai/ui/v2/icon" +import { pathToFileUrl, withFileDragImage, type Kind } from "@/components/file-tree" +import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual" +import { buildFileTreeV2Model, flattenFileTreeV2, normalizeFileTreeV2Path } from "@/components/file-tree-v2-model" +import { virtualScrollElement } from "@/components/virtual-scroll-element" + +export type { Kind } from "@/components/file-tree" + +const INDENT_STEP = 16 + +function rowPaddingLeft(level: number, type: FileNode["type"]) { + if (type === "directory") return 8 + level * INDENT_STEP + if (level === 0) return 8 + return 8 + level * INDENT_STEP - INDENT_STEP +} + +function guideLineLeft(level: number) { + return rowPaddingLeft(level, "directory") + 8 +} + +export const kindLabel = (kind: Kind) => { + if (kind === "add") return "A" + if (kind === "del") return "D" + return "" +} + +export const kindChange = (kind: Kind) => { + if (kind === "add") return "added" + if (kind === "del") return "deleted" + return "modified" +} + +const FileTreeNodeV2 = ( + p: ParentProps & + ComponentProps<"div"> & + ComponentProps<"button"> & { + node: FileNode + level: number + active?: string + draggable: boolean + kinds?: ReadonlyMap + as?: "div" | "button" + }, +) => { + const [local, rest] = splitProps(p, [ + "node", + "level", + "active", + "draggable", + "kinds", + "as", + "children", + "class", + "classList", + ]) + const kind = () => local.kinds?.get(local.node.path) + + return ( + { + if (!local.draggable) return + event.dataTransfer?.setData("text/plain", `file:${local.node.path}`) + event.dataTransfer?.setData("text/uri-list", pathToFileUrl(local.node.path)) + if (event.dataTransfer) event.dataTransfer.effectAllowed = "copy" + withFileDragImage(event) + }} + {...rest} + > + {local.children} + {local.node.name} + {(() => { + const value = kind() + if (!value || local.node.type !== "file") return null + return ( + + {kindLabel(value)} + + ) + })()} + + ) +} + +function GuideLines(props: { level: number }) { + return ( + + {(_, index) => ( +
+ )} + + ) +} + +export default function FileTreeV2(props: { + active?: string + allowed?: readonly string[] + kinds?: ReadonlyMap + draggable?: boolean + onFileClick?: (file: FileNode) => void +}) { + const file = useFile() + const draggable = () => props.draggable ?? true + const active = () => normalizeFileTreeV2Path(props.active ?? "") + const model = createMemo(() => buildFileTreeV2Model(props.allowed ?? [])) + const rows = createMemo(() => flattenFileTreeV2(model(), (path) => file.tree.state(path)?.expanded ?? true)) + const [root, setRoot] = createSignal() + const [focused, setFocused] = createSignal() + const virtualizer = createVirtualizer({ + get count() { + return rows().length + }, + getScrollElement: () => virtualScrollElement(root()), + initialRect: { width: 0, height: 600 }, + estimateSize: () => 28, + gap: 2, + overscan: 10, + get getItemKey() { + const current = rows() + return (index: number) => current[index]?.node.path ?? index + }, + rangeExtractor: (range) => { + const indexes = defaultRangeExtractor(range) + const path = focused() + const index = path ? rows().findIndex((row) => row.node.path === path) : -1 + if (index < 0 || indexes.includes(index)) return indexes + return [...indexes, index].sort((a, b) => a - b) + }, + }) + createEffect(() => { + const path = active() + if (!path) return + const index = rows().findIndex((row) => row.node.path === path) + if (index < 0) return + queueMicrotask(() => { + if (virtualizer.range && index >= virtualizer.range.startIndex && index <= virtualizer.range.endIndex) return + virtualizer.scrollToIndex(index, { align: "auto" }) + }) + }) + const rowByKey = createMemo(() => new Map(rows().map((row) => [row.node.path, row] as const))) + const virtualItemByKey = createMemo( + () => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)), + ) + const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key)) + + return ( +
+ + {(key) => ( + + {(item) => ( +
+ + {(row) => ( + setFocused(row().node.path)} + onBlur={() => setFocused(undefined)} + onClick={() => + props.onFileClick?.({ + ...row().node, + path: row().node.originalPath, + absolute: row().node.originalPath, + }) + } + > + + 0}> +
+ + + + + + + } + > + setFocused(row().node.path)} + onBlur={() => setFocused(undefined)} + aria-expanded={file.tree.state(row().node.path)?.expanded ?? true} + onClick={() => + file.tree.state(row().node.path)?.expanded === false + ? file.tree.expand(row().node.path, { list: false }) + : file.tree.collapse(row().node.path) + } + > + +
+ +
+
+ + )} + +
+ )} +
+ )} + +
+ ) +} diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 211ce05ef065..718e2f3493de 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -21,13 +21,13 @@ import type { FileNode } from "@opencode-ai/sdk/v2" const MAX_DEPTH = 128 -function pathToFileUrl(filepath: string): string { +export function pathToFileUrl(filepath: string): string { return `file://${encodeFilePath(filepath)}` } -type Kind = "add" | "del" | "mix" +export type Kind = "add" | "del" | "mix" -type Filter = { +export type Filter = { files: Set dirs: Set } @@ -78,7 +78,7 @@ const kindDotColor = (kind: Kind) => { return "background-color: var(--icon-diff-modified-base)" } -const visibleKind = (node: FileNode, kinds?: ReadonlyMap, marks?: Set) => { +export const visibleKind = (node: FileNode, kinds?: ReadonlyMap, marks?: Set) => { const kind = kinds?.get(node.path) if (!kind) return if (!marks?.has(node.path)) return @@ -99,7 +99,7 @@ const buildDragImage = (target: HTMLElement) => { return image } -const withFileDragImage = (event: DragEvent) => { +export const withFileDragImage = (event: DragEvent) => { const image = buildDragImage(event.currentTarget as HTMLElement) if (!image) return document.body.appendChild(image) @@ -201,6 +201,7 @@ export default function FileTree(props: { kinds?: ReadonlyMap draggable?: boolean onFileClick?: (file: FileNode) => void + onFileDoubleClick?: (file: FileNode) => void _filter?: Filter _marks?: Set @@ -440,6 +441,7 @@ export default function FileTree(props: { active={props.active} draggable={props.draggable} onFileClick={props.onFileClick} + onFileDoubleClick={props.onFileDoubleClick} _filter={filter()} _marks={marks()} _deeps={deeps()} @@ -462,6 +464,7 @@ export default function FileTree(props: { as="button" type="button" onClick={() => props.onFileClick?.(node)} + onDblClick={() => props.onFileDoubleClick?.(node)} >
diff --git a/packages/app/src/components/help-button.tsx b/packages/app/src/components/help-button.tsx index 9109418fcf59..de156e1b33c6 100644 --- a/packages/app/src/components/help-button.tsx +++ b/packages/app/src/components/help-button.tsx @@ -1,54 +1,144 @@ -import { Icon } from "@opencode-ai/ui/v2/icon" -import { Popover } from "@opencode-ai/ui/popover" +import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" +import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { createSignal, Show } from "solid-js" import { createStore } from "solid-js/store" +import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer" +import { usePlatform } from "@/context/platform" +import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4" +import { Persist, persisted } from "@/utils/persist" + +const helpIcon = ( + +) + +const triggerClass = + "size-7 !rounded-full shrink-0 bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]" + +// TODO: wire to changelog / seen-state when available +const showPopover = () => true export function HelpButton() { if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null - const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */ - const [shown, setShown] = createSignal(false) + const platform = usePlatform() return ( - -