chore: save local dev updates

This commit is contained in:
777genius 2026-05-16 20:03:30 +03:00
parent 09db3abfcd
commit bfebdff3cf
11 changed files with 335 additions and 15 deletions

View file

@ -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).
---

View 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>

View file

@ -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

View file

@ -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": [

View 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);
+ });
}, [])
};
}

View file

@ -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)

View file

@ -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

View file

@ -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({

View file

@ -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),
};
}

View file

@ -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;
}

View 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();
});
});
});