chore: save local dev updates
This commit is contained in:
parent
09db3abfcd
commit
bfebdff3cf
11 changed files with 335 additions and 15 deletions
|
|
@ -191,10 +191,10 @@ For feature architecture and implementation guidance:
|
|||
| **Git worktree isolation** | ✅ Optional | ✅ Core primitive | ✅ Worktrees / branches | ⚠️ Background branches/VMs | ⚠️ Manual worktrees |
|
||||
| **Multi-agent backend** | ✅ Claude, Codex + OpenCode teammates | ✅ Claude, Codex, Gemini, Copilot + more | ✅ BYO agents: Claude, Codex, Cursor/OpenCode, HTTP | ⚠️ Multi-model agents, no team backend | ⚠️ Claude-only experimental teams |
|
||||
| **Org chart / governance** | ⚠️ Roles + approvals, no org chart | ⚠️ Roles + escalation | ✅ Org chart + board governance | ⚠️ Team admin only | ❌ |
|
||||
| **Budget controls** | ⚠️ Cost/token visibility, no hard caps | ⚠️ Cost tiers + digest, no hard caps | ✅ Per-agent budgets + hard stops | ⚠️ Usage + BG spend limits | ⚠️ `/cost` + workspace limits |
|
||||
| **Budget controls** | ⚠️ Cost/token visibility, no hard caps | ⚠️ Cost tiers + digest, no hard caps | ✅ Per-agent budgets + hard stops | ⚠️ Usage + BG spend limits | ⚠️ `/usage` + workspace limits |
|
||||
| **Price** | **Free OSS UI**, provider access needed | Free OSS, runtime plans needed | Free OSS, self-hosted + infra | Free + paid usage | Claude plan or API usage |
|
||||
|
||||
Fact sources checked on May 5, 2026: [detailed research notes](docs/research/gastown-paperclip-comparison-2026-05-05.md), [Gastown README](https://github.com/gastownhall/gastown), [Gastown provider guide](https://github.com/gastownhall/gastown/blob/main/docs/agent-provider-integration.md), [Gastown scheduler](https://github.com/gastownhall/gastown/blob/main/docs/design/scheduler.md), [Paperclip README](https://github.com/paperclipai/paperclip), [Paperclip adapters](https://github.com/paperclipai/paperclip/blob/master/docs/adapters/overview.md), [Paperclip budgets](https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/costs-and-budgets.md), [Paperclip runtime services](https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/execution-workspaces-and-runtime-services.md), [Paperclip Kanban source](https://github.com/paperclipai/paperclip/blob/master/ui/src/components/KanbanBoard.tsx), [Cursor Background Agents](https://docs.cursor.com/en/background-agents), [Cursor Diffs & Review](https://docs.cursor.com/en/agent/review), [Cursor Bugbot](https://docs.cursor.com/en/bugbot), [Cursor pricing](https://docs.cursor.com/en/account/usage), [Claude Code agent teams](https://code.claude.com/docs/en/agent-teams), [Claude Code subagents](https://code.claude.com/docs/en/sub-agents), [Claude Code workflows](https://code.claude.com/docs/en/common-workflows), [Claude Code costs](https://code.claude.com/docs/en/costs), [Claude pricing](https://claude.com/pricing).
|
||||
Fact sources checked on May 16, 2026: [detailed research notes](docs/research/gastown-paperclip-comparison-2026-05-16.md), [Gastown README](https://github.com/gastownhall/gastown), [Gastown provider guide](https://github.com/gastownhall/gastown/blob/main/docs/agent-provider-integration.md), [Gastown scheduler](https://github.com/gastownhall/gastown/blob/main/docs/design/scheduler.md), [Gastown release](https://github.com/gastownhall/gastown/releases/tag/v1.1.0), [Paperclip README](https://github.com/paperclipai/paperclip), [Paperclip adapters](https://github.com/paperclipai/paperclip/blob/master/docs/adapters/overview.md), [Paperclip budgets](https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/costs-and-budgets.md), [Paperclip runtime services](https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/execution-workspaces-and-runtime-services.md), [Paperclip Kanban source](https://github.com/paperclipai/paperclip/blob/master/ui/src/components/KanbanBoard.tsx), [Paperclip work products](https://github.com/paperclipai/paperclip/blob/master/packages/shared/src/validators/work-product.ts), [Paperclip release](https://github.com/paperclipai/paperclip/releases/tag/v2026.513.0), [Cursor Background Agents](https://docs.cursor.com/en/background-agents), [Cursor Diffs & Review](https://docs.cursor.com/en/agent/review), [Cursor Bugbot](https://docs.cursor.com/en/bugbot), [Cursor pricing](https://docs.cursor.com/en/account/usage), [Claude Code agent teams](https://code.claude.com/docs/en/agent-teams), [Claude Code subagents](https://code.claude.com/docs/en/sub-agents), [Claude Code workflows](https://code.claude.com/docs/en/common-workflows), [Claude Code costs](https://code.claude.com/docs/en/costs), [Claude pricing](https://claude.com/pricing).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
107
docs/research/gastown-paperclip-comparison-2026-05-16.md
Normal file
107
docs/research/gastown-paperclip-comparison-2026-05-16.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Gastown и Paperclip comparison для лендинга и README
|
||||
|
||||
> Дата проверки: 2026-05-16
|
||||
> Цель: публичная таблица `Agent Teams | Gastown | Paperclip | Cursor | Claude Code CLI` без угадываний по конкурентам.
|
||||
> Метод: `gh repo view`, `gh api` по первичным GitHub-файлам, официальные docs Cursor и Claude Code, страница Claude pricing.
|
||||
|
||||
## Snapshot
|
||||
|
||||
| Проект | Позиционирование | Статус на 2026-05-16 | Лицензия |
|
||||
|---|---|---:|---|
|
||||
| **Gastown** | multi-agent workspace manager для coding agents | `15,228★`, latest `v1.1.0` от `2026-05-07`, push `2026-05-15` | MIT |
|
||||
| **Paperclip** | control plane для autonomous AI companies | `65,796★`, latest `v2026.513.0` от `2026-05-13`, push `2026-05-16` | MIT |
|
||||
|
||||
## Что изменилось после проверки 2026-05-05
|
||||
|
||||
- **Gastown**: свежий GitHub snapshot изменился с `v1.0.1` на `v1.1.0`; README/provider/scheduler факты для публичной таблицы остались валидными.
|
||||
- **Paperclip**: свежий GitHub snapshot изменился с `v2026.428.0` на `v2026.513.0`; README/adapters/budget/runtime/Kanban facts остались валидными.
|
||||
- **Claude Code costs**: официальный cost guide теперь называет `/usage` как команду для session token/cost tracking. Поэтому публичная строка `Budget controls` для Claude Code CLI обновлена с `/cost + workspace limits` на `/usage + workspace limits`.
|
||||
- **Claude pricing**: Team pricing page явно включает Claude Code в Team seats; публичная строка `Claude plan or API usage` остаётся корректной.
|
||||
- **Cursor**: official docs по Background Agents, Diffs & Review, Bugbot и usage/pricing по-прежнему поддерживают текущие формулировки таблицы. Background Agents остаются remote/async agents on separate branches/VMs with auto-run terminal commands; Bugbot остаётся PR-review product with its own pricing.
|
||||
|
||||
## Проверенные публичные формулировки
|
||||
|
||||
### Gastown
|
||||
|
||||
- README по-прежнему позиционирует Gas Town как workspace manager для Claude Code, GitHub Copilot, Codex, Gemini и других coding agents.
|
||||
- Provider guide по-прежнему описывает tmux/provider contract для Claude, Gemini, Codex, Cursor, AMP, OpenCode, Copilot и других.
|
||||
- Scheduler docs по-прежнему подтверждают `scheduler.max_polecats`, deferred dispatch, capacity governor, pause/resume и daemon dispatch cycle.
|
||||
- Dashboard остаётся monitoring view for agents, convoys, hooks, queues, issues and escalations, а не Kanban product.
|
||||
- Refinery merge queue есть, но это не hunk-level diff review UI.
|
||||
|
||||
Публичная оценка не меняется:
|
||||
|
||||
- `Task dependencies` - `✅ Beads DAG waves`
|
||||
- `Kanban board` - `❌ Dashboard, not Kanban`
|
||||
- `Per-task code review` - `⚠️ Merge queue, no diff UI`
|
||||
- `Budget controls` - `⚠️ Cost tiers + digest, no hard caps`
|
||||
|
||||
### Paperclip
|
||||
|
||||
- README по-прежнему описывает org charts, budgets, governance, goal alignment and agent coordination.
|
||||
- Adapter overview подтверждает Claude Local, Codex Local, Gemini experimental, OpenCode Local, Cursor, OpenClaw Gateway, Process and HTTP adapters.
|
||||
- Budget docs подтверждают per-agent monthly budgets, warning threshold at 80%, hard stop at 100%, auto-pause and no more heartbeats.
|
||||
- Runtime services docs подтверждают manual UI-managed services/jobs and execution workspaces with isolated checkout/branch/runtime state.
|
||||
- Kanban source по-прежнему содержит `backlog`, `todo`, `in_progress`, `in_review`, `blocked`, `done`, `cancelled` and `@dnd-kit`.
|
||||
- Work product validators подтверждают `preview_url`, `runtime_service`, `pull_request`, `branch`, `commit`, `artifact`, `document` and review statuses.
|
||||
|
||||
Публичная оценка не меняется:
|
||||
|
||||
- `Kanban board` - `✅ 7 columns, drag-and-drop`
|
||||
- `Per-task code review` - `⚠️ PR/work products, no inline diff`
|
||||
- `Hunk-level review` - `❌ Bring your own review`
|
||||
- `Budget controls` - `✅ Per-agent budgets + hard stops`
|
||||
|
||||
### Cursor
|
||||
|
||||
- Background Agents are asynchronous remote agents that edit/run code in isolated machines, use GitHub branches, support follow-ups and can auto-run terminal commands.
|
||||
- Diffs & Review still supports diff review with accept/reject flows and selective acceptance.
|
||||
- Bugbot still focuses on PR review and comments with explanations/fix suggestions; pricing remains separate from normal Cursor subscriptions.
|
||||
- Usage/pricing docs still describe Free + paid usage, included agent usage by plan, dashboard token/usage breakdowns, background-agent access and spend limits.
|
||||
|
||||
Публичная оценка не меняется:
|
||||
|
||||
- `Full autonomy` - `⚠️ Background agents, not teams`
|
||||
- `Hunk-level review` - `✅`
|
||||
- `Review workflow` - `⚠️ PR/BugBot only`
|
||||
- `Flexible autonomy` - `⚠️ BG agents auto-run commands`
|
||||
- `Price` - `Free + paid usage`
|
||||
|
||||
### Claude Code CLI
|
||||
|
||||
- Agent teams are still experimental and disabled by default through `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`.
|
||||
- Official docs still confirm shared task list, mailbox, direct teammate messaging, task dependencies, plan approval requests, quality-gate hooks and local team/task storage.
|
||||
- Agent teams require Claude Code `v2.1.32` or later according to the docs.
|
||||
- Worktrees remain an official workflow for isolated sessions, but this is not the same as a product-level worktree strategy UI.
|
||||
- Cost docs now use `/usage` for detailed token usage statistics, plus workspace spend limits and usage reporting in Console for API users.
|
||||
|
||||
Публичная оценка после свежей проверки:
|
||||
|
||||
- `Agent-to-agent messaging` - `✅ Team mailbox, no UI`
|
||||
- `Linked tasks` - `✅ Shared task list`
|
||||
- `Task dependencies` - `✅ Team task deps, no UI`
|
||||
- `Budget controls` - `⚠️ /usage + workspace limits`
|
||||
- `Multi-agent backend` - `⚠️ Claude-only experimental teams`
|
||||
|
||||
## Источники
|
||||
|
||||
- Gastown repo: <https://github.com/gastownhall/gastown>
|
||||
- Gastown v1.1.0: <https://github.com/gastownhall/gastown/releases/tag/v1.1.0>
|
||||
- Gastown provider guide: <https://github.com/gastownhall/gastown/blob/main/docs/agent-provider-integration.md>
|
||||
- Gastown scheduler docs: <https://github.com/gastownhall/gastown/blob/main/docs/design/scheduler.md>
|
||||
- Paperclip repo: <https://github.com/paperclipai/paperclip>
|
||||
- Paperclip v2026.513.0: <https://github.com/paperclipai/paperclip/releases/tag/v2026.513.0>
|
||||
- Paperclip adapters: <https://github.com/paperclipai/paperclip/blob/master/docs/adapters/overview.md>
|
||||
- Paperclip costs and budgets docs: <https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/costs-and-budgets.md>
|
||||
- Paperclip runtime services docs: <https://github.com/paperclipai/paperclip/blob/master/docs/guides/board-operator/execution-workspaces-and-runtime-services.md>
|
||||
- Paperclip Kanban source: <https://github.com/paperclipai/paperclip/blob/master/ui/src/components/KanbanBoard.tsx>
|
||||
- Paperclip work products source: <https://github.com/paperclipai/paperclip/blob/master/packages/shared/src/validators/work-product.ts>
|
||||
- Cursor Background Agents: <https://docs.cursor.com/en/background-agents>
|
||||
- Cursor Diffs & Review: <https://docs.cursor.com/en/agent/review>
|
||||
- Cursor Bugbot: <https://docs.cursor.com/en/bugbot>
|
||||
- Cursor usage/pricing: <https://docs.cursor.com/en/account/usage>
|
||||
- Claude Code agent teams: <https://code.claude.com/docs/en/agent-teams>
|
||||
- Claude Code subagents: <https://code.claude.com/docs/en/sub-agents>
|
||||
- Claude Code common workflows: <https://code.claude.com/docs/en/common-workflows>
|
||||
- Claude Code costs: <https://code.claude.com/docs/en/costs>
|
||||
- Claude pricing: <https://claude.com/pricing>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Реальные конкуренты для Comparison в README
|
||||
|
||||
> ⚠️ Update 2026-05-05: публичная таблица README/landing теперь сравнивает нас с `Gastown` и `Paperclip`, а не с `Claude Code Agent Teams` и `GoClaw`. Актуальная research-опора: [gastown-paperclip-comparison-2026-05-05.md](gastown-paperclip-comparison-2026-05-05.md). Ниже оставлен старый broader draft как исторический контекст.
|
||||
> ⚠️ Update 2026-05-16: публичная таблица README/landing теперь сравнивает нас с `Gastown` и `Paperclip`, а не с `Claude Code Agent Teams` и `GoClaw`. Актуальная research-опора: [gastown-paperclip-comparison-2026-05-16.md](gastown-paperclip-comparison-2026-05-16.md). Ниже оставлен старый broader draft как исторический контекст.
|
||||
|
||||
> Дата проверки: 2026-04-13
|
||||
> Статус: внутренний comparison draft
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@
|
|||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-presence": "^1.1.5",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
|
|
@ -350,7 +351,10 @@
|
|||
"electron",
|
||||
"node-pty",
|
||||
"cpu-features"
|
||||
]
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"@radix-ui/react-presence@1.1.5": "patches/@radix-ui__react-presence@1.1.5.patch"
|
||||
}
|
||||
},
|
||||
"knip": {
|
||||
"entry": [
|
||||
|
|
|
|||
86
patches/@radix-ui__react-presence@1.1.5.patch
Normal file
86
patches/@radix-ui__react-presence@1.1.5.patch
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 944abc2652716a5047360c8abc2d48e700a3f3f8..e0d98a35aead8e057a304734627e0022643cb71c 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -62,6 +62,8 @@ var Presence = (props) => {
|
||||
Presence.displayName = "Presence";
|
||||
function usePresence(present) {
|
||||
const [node, setNode] = React2.useState();
|
||||
+ const nodeRef = React2.useRef();
|
||||
+ const nodeCleanupGenerationRef = React2.useRef(0);
|
||||
const stylesRef = React2.useRef(null);
|
||||
const prevPresentRef = React2.useRef(present);
|
||||
const prevAnimationNameRef = React2.useRef("none");
|
||||
@@ -146,8 +148,27 @@ function usePresence(present) {
|
||||
return {
|
||||
isPresent: ["mounted", "unmountSuspended"].includes(state),
|
||||
ref: React2.useCallback((node2) => {
|
||||
- stylesRef.current = node2 ? getComputedStyle(node2) : null;
|
||||
- setNode(node2);
|
||||
+ const syncNode = (nextNode) => {
|
||||
+ if (nodeRef.current === nextNode) {
|
||||
+ stylesRef.current = nextNode ? getComputedStyle(nextNode) : null;
|
||||
+ return;
|
||||
+ }
|
||||
+ nodeRef.current = nextNode;
|
||||
+ stylesRef.current = nextNode ? getComputedStyle(nextNode) : null;
|
||||
+ setNode(nextNode);
|
||||
+ };
|
||||
+ nodeCleanupGenerationRef.current += 1;
|
||||
+ const cleanupGeneration = nodeCleanupGenerationRef.current;
|
||||
+ if (node2) {
|
||||
+ syncNode(node2);
|
||||
+ return;
|
||||
+ }
|
||||
+ queueMicrotask(() => {
|
||||
+ if (nodeCleanupGenerationRef.current !== cleanupGeneration) {
|
||||
+ return;
|
||||
+ }
|
||||
+ syncNode(null);
|
||||
+ });
|
||||
}, [])
|
||||
};
|
||||
}
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 0efe02a3c45790c11f94f71563bf1db7257b799f..67db6319fb2af1008e2da62ec8cdb21e4903ea1e 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -26,6 +26,8 @@ var Presence = (props) => {
|
||||
Presence.displayName = "Presence";
|
||||
function usePresence(present) {
|
||||
const [node, setNode] = React2.useState();
|
||||
+ const nodeRef = React2.useRef();
|
||||
+ const nodeCleanupGenerationRef = React2.useRef(0);
|
||||
const stylesRef = React2.useRef(null);
|
||||
const prevPresentRef = React2.useRef(present);
|
||||
const prevAnimationNameRef = React2.useRef("none");
|
||||
@@ -110,8 +112,27 @@ function usePresence(present) {
|
||||
return {
|
||||
isPresent: ["mounted", "unmountSuspended"].includes(state),
|
||||
ref: React2.useCallback((node2) => {
|
||||
- stylesRef.current = node2 ? getComputedStyle(node2) : null;
|
||||
- setNode(node2);
|
||||
+ const syncNode = (nextNode) => {
|
||||
+ if (nodeRef.current === nextNode) {
|
||||
+ stylesRef.current = nextNode ? getComputedStyle(nextNode) : null;
|
||||
+ return;
|
||||
+ }
|
||||
+ nodeRef.current = nextNode;
|
||||
+ stylesRef.current = nextNode ? getComputedStyle(nextNode) : null;
|
||||
+ setNode(nextNode);
|
||||
+ };
|
||||
+ nodeCleanupGenerationRef.current += 1;
|
||||
+ const cleanupGeneration = nodeCleanupGenerationRef.current;
|
||||
+ if (node2) {
|
||||
+ syncNode(node2);
|
||||
+ return;
|
||||
+ }
|
||||
+ queueMicrotask(() => {
|
||||
+ if (nodeCleanupGenerationRef.current !== cleanupGeneration) {
|
||||
+ return;
|
||||
+ }
|
||||
+ syncNode(null);
|
||||
+ });
|
||||
}, [])
|
||||
};
|
||||
}
|
||||
|
|
@ -8,6 +8,11 @@ overrides:
|
|||
lodash-es: ^4.18.1
|
||||
uuid: ^11.1.1
|
||||
|
||||
patchedDependencies:
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
hash: afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e
|
||||
path: patches/@radix-ui__react-presence@1.1.5.patch
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
|
|
@ -138,6 +143,9 @@ importers:
|
|||
'@radix-ui/react-popover':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence':
|
||||
specifier: ^1.1.5
|
||||
version: 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -13909,7 +13917,7 @@ snapshots:
|
|||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -13926,7 +13934,7 @@ snapshots:
|
|||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -13984,7 +13992,7 @@ snapshots:
|
|||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -14055,7 +14063,7 @@ snapshots:
|
|||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
|
|
@ -14093,7 +14101,7 @@ snapshots:
|
|||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -14117,7 +14125,7 @@ snapshots:
|
|||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -14157,7 +14165,7 @@ snapshots:
|
|||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@radix-ui/react-presence@1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -14251,7 +14259,7 @@ snapshots:
|
|||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
@ -14270,7 +14278,7 @@ snapshots:
|
|||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,10 @@ const DashboardRateLimitChips = ({
|
|||
>
|
||||
{item.label}
|
||||
</span>
|
||||
<span className="text-xs font-medium" style={{ color: '#86efac' }}>
|
||||
<span
|
||||
className="text-xs font-medium"
|
||||
style={{ color: item.isDepleted ? '#f87171' : '#86efac' }}
|
||||
>
|
||||
{item.remaining}
|
||||
</span>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -121,11 +121,13 @@ describe('providerDashboardRateLimits', () => {
|
|||
label: '5h left',
|
||||
remaining: '75%',
|
||||
resetsAt: 'reset unknown',
|
||||
isDepleted: false,
|
||||
},
|
||||
{
|
||||
label: 'Weekly left',
|
||||
remaining: '50%',
|
||||
resetsAt: 'reset unknown',
|
||||
isDepleted: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -188,10 +190,42 @@ describe('providerDashboardRateLimits', () => {
|
|||
label: '5h left',
|
||||
remaining: '80%',
|
||||
resetsAt: 'reset unknown',
|
||||
isDepleted: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('marks fully depleted limits when no quota remains', () => {
|
||||
const connection = createCodexConnection();
|
||||
|
||||
const items = getCodexDashboardRateLimits(
|
||||
createProvider({
|
||||
providerId: 'codex',
|
||||
displayName: 'Codex',
|
||||
authMethod: 'oauth_token',
|
||||
connection: {
|
||||
...connection,
|
||||
codex: {
|
||||
...connection.codex!,
|
||||
rateLimits: {
|
||||
...connection.codex!.rateLimits!,
|
||||
primary: {
|
||||
usedPercent: 100,
|
||||
windowDurationMins: 300,
|
||||
resetsAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(items?.[0]).toMatchObject({
|
||||
remaining: '0%',
|
||||
isDepleted: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('shows Anthropic rate limit skeletons when subscription mode is selected in config', () => {
|
||||
expect(
|
||||
shouldShowDashboardRateLimitSkeleton({
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface DashboardRateLimitItem {
|
|||
label: string;
|
||||
remaining: string;
|
||||
resetsAt: string;
|
||||
isDepleted: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardRateLimitSkeletonModeInput {
|
||||
|
|
@ -180,6 +181,10 @@ function formatDashboardResetTime(timestampSeconds: number | null | undefined):
|
|||
});
|
||||
}
|
||||
|
||||
function isRateLimitDepleted(usedPercent: number | null | undefined): boolean {
|
||||
return typeof usedPercent === 'number' && Number.isFinite(usedPercent) && usedPercent >= 100;
|
||||
}
|
||||
|
||||
function buildRateLimitItem(
|
||||
label: string,
|
||||
usedPercent: number,
|
||||
|
|
@ -189,6 +194,7 @@ function buildRateLimitItem(
|
|||
label,
|
||||
remaining: formatCodexRemainingPercent(usedPercent) ?? 'Unknown',
|
||||
resetsAt: formatDashboardResetTime(resetsAt),
|
||||
isDepleted: isRateLimitDepleted(usedPercent),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface ProjectPathProject extends Project {
|
|||
filesystemState?: DashboardRecentProjectFilesystemState;
|
||||
}
|
||||
|
||||
export interface ProjectPathOptionMeta {
|
||||
export interface ProjectPathOptionMeta extends Record<string, unknown> {
|
||||
discoverySource?: DashboardRecentProjectSource;
|
||||
filesystemState?: DashboardRecentProjectFilesystemState;
|
||||
}
|
||||
|
|
|
|||
72
src/renderer/components/ui/presence.react19.test.tsx
Normal file
72
src/renderer/components/ui/presence.react19.test.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import React, { act, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { Presence } from '@radix-ui/react-presence';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const flushMicrotasks = async (): Promise<void> => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
};
|
||||
|
||||
describe('Radix Presence React 19 compatibility', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('does not recurse when a composed child ref changes identity and returns cleanup', async () => {
|
||||
const refEvents: string[] = [];
|
||||
|
||||
const Harness = (): React.JSX.Element => {
|
||||
const [tick, setTick] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button type="button" onClick={() => setTick((value) => value + 1)}>
|
||||
rerender
|
||||
</button>
|
||||
<Presence present>
|
||||
<div
|
||||
ref={(node) => {
|
||||
refEvents.push(node ? 'node' : 'null');
|
||||
return () => {
|
||||
refEvents.push('cleanup');
|
||||
};
|
||||
}}
|
||||
>
|
||||
tick {tick}
|
||||
</div>
|
||||
</Presence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const host = document.createElement('div');
|
||||
document.body.appendChild(host);
|
||||
const root = createRoot(host);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<Harness />);
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
host.querySelector<HTMLButtonElement>('button')?.click();
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
expect(host.textContent).toContain('tick 1');
|
||||
expect(refEvents.length).toBeLessThan(10);
|
||||
expect(refEvents).toContain('cleanup');
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
await flushMicrotasks();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue