refactor(claude): drop the redundant Team-tab account budget (T-158)
After live review: the shared account /usage budget read as redundant on both the Activity and Team tabs. Usage is per-account and can't be split per member, so one place is enough — it lives only on the Activity tab, next to the refresh control that fetches it (T-415). Removes the Team-tab account card, its usage prop wiring, the team.section.usage/team.usage.shared i18n keys, and the now-obsolete team_tab test. The three-tab card facelift stays. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6587,3 +6587,200 @@ Dropdown/select listing registered accounts + a "(default)" option. Selecting is
|
|||||||
|
|
||||||
- The Claude pane chrome badge + welcome view — T-481.
|
- The Claude pane chrome badge + welcome view — T-481.
|
||||||
', NULL, '2026-06-25 10:24:31', '2026-06-25 10:24:31', '2026-06-25 10:24:31', NULL, 'fd3c9504dad8bae438c1df879bd5eb44', 2) ON CONFLICT(hash) DO NOTHING;
|
', NULL, '2026-06-25 10:24:31', '2026-06-25 10:24:31', '2026-06-25 10:24:31', NULL, 'fd3c9504dad8bae438c1df879bd5eb44', 2) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'description', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.
|
||||||
|
|
||||||
|
2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).
|
||||||
|
|
||||||
|
UNBLOCKED (2026-06-12, T-415): probed claude 2.1.175 — a forwarded /usage IS
|
||||||
|
answered headless in stream-json (free, num_turns 0) with parseable text
|
||||||
|
(session %, week % all-models, week % Sonnet). The Activity tab now renders it
|
||||||
|
via parseUsageText + a user-initiated refresh control. Remaining scope for this
|
||||||
|
ticket would be per-member/team budget split, if still wanted.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
REFINED 2026-06-26
|
||||||
|
|
||||||
|
## Grounding (current state)
|
||||||
|
- T-415 SHIPPED the account-budget display. `/usage` is forwarded headless
|
||||||
|
(free, num_turns 0), parsed by `parseUsageText` → `ClaudeUsage` (session %,
|
||||||
|
week all-models %, week Sonnet %) in
|
||||||
|
lib/builtin/claude/src/claude_status.dart, rendered in the Activity tab
|
||||||
|
(meta_sidebar/activity_tab.dart, USAGE `MetaSection`) with a user-initiated
|
||||||
|
refresh that publishes `/usage` on the `builtin.claude/command` channel.
|
||||||
|
claude_meta_sidebar.dart binds the PRIMARY session''s synthetic text items and
|
||||||
|
parses them into `_usage`.
|
||||||
|
|
||||||
|
## KEY FINDING — a literal "per-member split" is not meaningful today
|
||||||
|
- Claude''s usage budget is per-ACCOUNT (subscription/OAuth), not per-session.
|
||||||
|
Every clide session in a workspace (primary, secondaries, managed teammates)
|
||||||
|
spawns against the SAME `~/.claude` login — session_orchestrator spawns with
|
||||||
|
no per-session CLAUDE_* auth override and all transcripts live under one
|
||||||
|
project dir. So the whole team draws down ONE shared 5h/weekly budget.
|
||||||
|
- Rendering the same numbers on each roster row would mislead, not inform. True
|
||||||
|
per-member budgets need per-SESSION account login, which even T-476 (per-REPO
|
||||||
|
login) does NOT provide — within one workspace/team the account is shared.
|
||||||
|
Treat per-member-different-account as out of scope unless per-session
|
||||||
|
multi-account auth ever exists.
|
||||||
|
|
||||||
|
## RECOMMENDED SCOPE (shippable, honest) — resolves Q-34
|
||||||
|
Surface the existing account-wide budget in the TEAM tab as a single "Account"
|
||||||
|
section (NOT per-member): the team sidebar then answers "how much budget is left
|
||||||
|
for the account the whole team shares." Satisfies the ticket title without the
|
||||||
|
false split.
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
1. meta_sidebar/team_tab.dart (TeamTabView.build): add a `MetaSection`
|
||||||
|
(header i18n key `team.section.usage`) rendering the same 3 `MetaRow`s the
|
||||||
|
Activity tab uses (session / week-all / week-sonnet), shown only when usage
|
||||||
|
!= null, with a one-line caption that it is account-wide / shared across the
|
||||||
|
team (so it isn''t read as per-member).
|
||||||
|
2. claude_meta_sidebar.dart: pass the existing `_usage` (ClaudeUsage?) into
|
||||||
|
TeamTabView (it already passes `members`). No new fetch — the primary''s
|
||||||
|
`/usage` already IS the account budget.
|
||||||
|
3. Optional: a refresh control in the team section mirroring the Activity tab
|
||||||
|
(publish `/usage` on `builtin.claude/command`), or just rely on the Activity
|
||||||
|
tab''s refresh keeping `_usage` current.
|
||||||
|
|
||||||
|
Files to touch: meta_sidebar/team_tab.dart, claude_meta_sidebar.dart (thread
|
||||||
|
usage), meta_sidebar/models.dart (reuse MetaSection/MetaRow),
|
||||||
|
assets/i18n/en_us + nl_nl/builtin.claude.json (new `team.section.usage` [+
|
||||||
|
caption] key — keep en/nl parity for the a11y i18n gate).
|
||||||
|
|
||||||
|
## ALTERNATIVE PATHS (pick one before building)
|
||||||
|
A. Shared-account section in the team tab (recommended above).
|
||||||
|
B. Close T-158 as substantially DONE by T-415 — the budget is already visible in
|
||||||
|
the Activity tab; a team-tab duplicate may be redundant. Q-34 answered either
|
||||||
|
way.
|
||||||
|
C. Keep the true per-member split as a FUTURE item, explicitly blocked on a
|
||||||
|
(currently non-existent) per-session multi-account login; park T-158 behind
|
||||||
|
it.
|
||||||
|
|
||||||
|
## ACCEPTANCE (for option A)
|
||||||
|
- Team tab shows ONE account-budget section (session / week / week-sonnet %)
|
||||||
|
when a `/usage` result is available, clearly labelled account-wide / shared.
|
||||||
|
- No per-member duplication of the same numbers.
|
||||||
|
- No new network/headless call beyond the existing user-initiated `/usage`
|
||||||
|
refresh (D-64 / POLICY: explicit user action only).
|
||||||
|
- en/nl parity for any new i18n keys.
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
T-415 (usage parse + Activity render); Q-34 (this resolves it); T-476 (per-REPO,
|
||||||
|
not per-session, account login — does NOT enable per-member budgets);
|
||||||
|
`parseUsageText` / `ClaudeUsage` (claude_status.dart); activity_tab.dart USAGE
|
||||||
|
block; team_tab.dart roster; claude_meta_sidebar.dart usage binding.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
DECISION 2026-06-26: go with option A.
|
||||||
|
|
||||||
|
Build the single shared-account budget section in the TEAM tab (account-wide,
|
||||||
|
clearly labelled — NOT per-member). Options B (close as done-by-T-415) and C
|
||||||
|
(per-member, deferred behind per-session multi-account) are declined: per-member
|
||||||
|
is not meaningful under one shared account, and the team tab should carry its
|
||||||
|
own budget view rather than send the user to the Activity tab.
|
||||||
|
|
||||||
|
Scope is now exactly the "RECOMMENDED SCOPE" above (team_tab.dart MetaSection +
|
||||||
|
thread _usage from claude_meta_sidebar.dart + team.section.usage i18n key,
|
||||||
|
en/nl). This resolves Q-34. Ready to pick up; not yet started.', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.
|
||||||
|
|
||||||
|
2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).
|
||||||
|
|
||||||
|
UNBLOCKED (2026-06-12, T-415): probed claude 2.1.175 — a forwarded /usage IS
|
||||||
|
answered headless in stream-json (free, num_turns 0) with parseable text
|
||||||
|
(session %, week % all-models, week % Sonnet). The Activity tab now renders it
|
||||||
|
via parseUsageText + a user-initiated refresh control. Remaining scope for this
|
||||||
|
ticket would be per-member/team budget split, if still wanted.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
REFINED 2026-06-26
|
||||||
|
|
||||||
|
## Grounding (current state)
|
||||||
|
- T-415 SHIPPED the account-budget display. `/usage` is forwarded headless
|
||||||
|
(free, num_turns 0), parsed by `parseUsageText` → `ClaudeUsage` (session %,
|
||||||
|
week all-models %, week Sonnet %) in
|
||||||
|
lib/builtin/claude/src/claude_status.dart, rendered in the Activity tab
|
||||||
|
(meta_sidebar/activity_tab.dart, USAGE `MetaSection`) with a user-initiated
|
||||||
|
refresh that publishes `/usage` on the `builtin.claude/command` channel.
|
||||||
|
claude_meta_sidebar.dart binds the PRIMARY session''s synthetic text items and
|
||||||
|
parses them into `_usage`.
|
||||||
|
|
||||||
|
## KEY FINDING — a literal "per-member split" is not meaningful today
|
||||||
|
- Claude''s usage budget is per-ACCOUNT (subscription/OAuth), not per-session.
|
||||||
|
Every clide session in a workspace (primary, secondaries, managed teammates)
|
||||||
|
spawns against the SAME `~/.claude` login — session_orchestrator spawns with
|
||||||
|
no per-session CLAUDE_* auth override and all transcripts live under one
|
||||||
|
project dir. So the whole team draws down ONE shared 5h/weekly budget.
|
||||||
|
- Rendering the same numbers on each roster row would mislead, not inform. True
|
||||||
|
per-member budgets need per-SESSION account login, which even T-476 (per-REPO
|
||||||
|
login) does NOT provide — within one workspace/team the account is shared.
|
||||||
|
Treat per-member-different-account as out of scope unless per-session
|
||||||
|
multi-account auth ever exists.
|
||||||
|
|
||||||
|
## RECOMMENDED SCOPE (shippable, honest) — resolves Q-34
|
||||||
|
Surface the existing account-wide budget in the TEAM tab as a single "Account"
|
||||||
|
section (NOT per-member): the team sidebar then answers "how much budget is left
|
||||||
|
for the account the whole team shares." Satisfies the ticket title without the
|
||||||
|
false split.
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
1. meta_sidebar/team_tab.dart (TeamTabView.build): add a `MetaSection`
|
||||||
|
(header i18n key `team.section.usage`) rendering the same 3 `MetaRow`s the
|
||||||
|
Activity tab uses (session / week-all / week-sonnet), shown only when usage
|
||||||
|
!= null, with a one-line caption that it is account-wide / shared across the
|
||||||
|
team (so it isn''t read as per-member).
|
||||||
|
2. claude_meta_sidebar.dart: pass the existing `_usage` (ClaudeUsage?) into
|
||||||
|
TeamTabView (it already passes `members`). No new fetch — the primary''s
|
||||||
|
`/usage` already IS the account budget.
|
||||||
|
3. Optional: a refresh control in the team section mirroring the Activity tab
|
||||||
|
(publish `/usage` on `builtin.claude/command`), or just rely on the Activity
|
||||||
|
tab''s refresh keeping `_usage` current.
|
||||||
|
|
||||||
|
Files to touch: meta_sidebar/team_tab.dart, claude_meta_sidebar.dart (thread
|
||||||
|
usage), meta_sidebar/models.dart (reuse MetaSection/MetaRow),
|
||||||
|
assets/i18n/en_us + nl_nl/builtin.claude.json (new `team.section.usage` [+
|
||||||
|
caption] key — keep en/nl parity for the a11y i18n gate).
|
||||||
|
|
||||||
|
## ALTERNATIVE PATHS (pick one before building)
|
||||||
|
A. Shared-account section in the team tab (recommended above).
|
||||||
|
B. Close T-158 as substantially DONE by T-415 — the budget is already visible in
|
||||||
|
the Activity tab; a team-tab duplicate may be redundant. Q-34 answered either
|
||||||
|
way.
|
||||||
|
C. Keep the true per-member split as a FUTURE item, explicitly blocked on a
|
||||||
|
(currently non-existent) per-session multi-account login; park T-158 behind
|
||||||
|
it.
|
||||||
|
|
||||||
|
## ACCEPTANCE (for option A)
|
||||||
|
- Team tab shows ONE account-budget section (session / week / week-sonnet %)
|
||||||
|
when a `/usage` result is available, clearly labelled account-wide / shared.
|
||||||
|
- No per-member duplication of the same numbers.
|
||||||
|
- No new network/headless call beyond the existing user-initiated `/usage`
|
||||||
|
refresh (D-64 / POLICY: explicit user action only).
|
||||||
|
- en/nl parity for any new i18n keys.
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
T-415 (usage parse + Activity render); Q-34 (this resolves it); T-476 (per-REPO,
|
||||||
|
not per-session, account login — does NOT enable per-member budgets);
|
||||||
|
`parseUsageText` / `ClaudeUsage` (claude_status.dart); activity_tab.dart USAGE
|
||||||
|
block; team_tab.dart roster; claude_meta_sidebar.dart usage binding.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
DECISION 2026-06-26: go with option A.
|
||||||
|
|
||||||
|
Build the single shared-account budget section in the TEAM tab (account-wide,
|
||||||
|
clearly labelled — NOT per-member). Options B (close as done-by-T-415) and C
|
||||||
|
(per-member, deferred behind per-session multi-account) are declined: per-member
|
||||||
|
is not meaningful under one shared account, and the team tab should carry its
|
||||||
|
own budget view rather than send the user to the Activity tab.
|
||||||
|
|
||||||
|
Scope is now exactly the "RECOMMENDED SCOPE" above (team_tab.dart MetaSection +
|
||||||
|
thread _usage from claude_meta_sidebar.dart + team.section.usage i18n key,
|
||||||
|
en/nl). This resolves Q-34. Ready to pick up; not yet started.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
REVISED 2026-06-27 (after live review): pivoted A → B.
|
||||||
|
|
||||||
|
Seeing the shared-account card on BOTH the Activity and Team tabs read as
|
||||||
|
redundant — usage is per-account and can''t be split per member, so one place
|
||||||
|
is enough. Removed the Team-tab account card (and its usage prop + i18n keys);
|
||||||
|
the budget now lives only on the Activity tab, next to the /usage refresh
|
||||||
|
control that fetches it (T-415). The three-tab card facelift stays. Q-34
|
||||||
|
answered: budget surfaces once, on Activity.', NULL, '2026-06-27 05:55:56', '2026-06-27 05:55:56.468', '2026-06-27 05:55:56.468', NULL, 'ba7329e54bf44c2cb5a9e7fb63e685f3', 2) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
|||||||
@@ -8332,3 +8332,107 @@ Dropdown/select listing registered accounts + a "(default)" option. Selecting is
|
|||||||
|
|
||||||
- The Claude pane chrome badge + welcome view — T-481.
|
- The Claude pane chrome badge + welcome view — T-481.
|
||||||
', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-25 09:16:42', '2026-06-25 10:24:31', NULL, 'a699defe53d009c44e5af7f9f38a0c15', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-25 09:16:42', '2026-06-25 10:24:31', NULL, 'a699defe53d009c44e5af7f9f38a0c15', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'task', NULL, 'Team sidebar: account/team token budget (blocked on upstream)', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.
|
||||||
|
|
||||||
|
2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).
|
||||||
|
|
||||||
|
UNBLOCKED (2026-06-12, T-415): probed claude 2.1.175 — a forwarded /usage IS
|
||||||
|
answered headless in stream-json (free, num_turns 0) with parseable text
|
||||||
|
(session %, week % all-models, week % Sonnet). The Activity tab now renders it
|
||||||
|
via parseUsageText + a user-initiated refresh control. Remaining scope for this
|
||||||
|
ticket would be per-member/team budget split, if still wanted.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
REFINED 2026-06-26
|
||||||
|
|
||||||
|
## Grounding (current state)
|
||||||
|
- T-415 SHIPPED the account-budget display. `/usage` is forwarded headless
|
||||||
|
(free, num_turns 0), parsed by `parseUsageText` → `ClaudeUsage` (session %,
|
||||||
|
week all-models %, week Sonnet %) in
|
||||||
|
lib/builtin/claude/src/claude_status.dart, rendered in the Activity tab
|
||||||
|
(meta_sidebar/activity_tab.dart, USAGE `MetaSection`) with a user-initiated
|
||||||
|
refresh that publishes `/usage` on the `builtin.claude/command` channel.
|
||||||
|
claude_meta_sidebar.dart binds the PRIMARY session''s synthetic text items and
|
||||||
|
parses them into `_usage`.
|
||||||
|
|
||||||
|
## KEY FINDING — a literal "per-member split" is not meaningful today
|
||||||
|
- Claude''s usage budget is per-ACCOUNT (subscription/OAuth), not per-session.
|
||||||
|
Every clide session in a workspace (primary, secondaries, managed teammates)
|
||||||
|
spawns against the SAME `~/.claude` login — session_orchestrator spawns with
|
||||||
|
no per-session CLAUDE_* auth override and all transcripts live under one
|
||||||
|
project dir. So the whole team draws down ONE shared 5h/weekly budget.
|
||||||
|
- Rendering the same numbers on each roster row would mislead, not inform. True
|
||||||
|
per-member budgets need per-SESSION account login, which even T-476 (per-REPO
|
||||||
|
login) does NOT provide — within one workspace/team the account is shared.
|
||||||
|
Treat per-member-different-account as out of scope unless per-session
|
||||||
|
multi-account auth ever exists.
|
||||||
|
|
||||||
|
## RECOMMENDED SCOPE (shippable, honest) — resolves Q-34
|
||||||
|
Surface the existing account-wide budget in the TEAM tab as a single "Account"
|
||||||
|
section (NOT per-member): the team sidebar then answers "how much budget is left
|
||||||
|
for the account the whole team shares." Satisfies the ticket title without the
|
||||||
|
false split.
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
1. meta_sidebar/team_tab.dart (TeamTabView.build): add a `MetaSection`
|
||||||
|
(header i18n key `team.section.usage`) rendering the same 3 `MetaRow`s the
|
||||||
|
Activity tab uses (session / week-all / week-sonnet), shown only when usage
|
||||||
|
!= null, with a one-line caption that it is account-wide / shared across the
|
||||||
|
team (so it isn''t read as per-member).
|
||||||
|
2. claude_meta_sidebar.dart: pass the existing `_usage` (ClaudeUsage?) into
|
||||||
|
TeamTabView (it already passes `members`). No new fetch — the primary''s
|
||||||
|
`/usage` already IS the account budget.
|
||||||
|
3. Optional: a refresh control in the team section mirroring the Activity tab
|
||||||
|
(publish `/usage` on `builtin.claude/command`), or just rely on the Activity
|
||||||
|
tab''s refresh keeping `_usage` current.
|
||||||
|
|
||||||
|
Files to touch: meta_sidebar/team_tab.dart, claude_meta_sidebar.dart (thread
|
||||||
|
usage), meta_sidebar/models.dart (reuse MetaSection/MetaRow),
|
||||||
|
assets/i18n/en_us + nl_nl/builtin.claude.json (new `team.section.usage` [+
|
||||||
|
caption] key — keep en/nl parity for the a11y i18n gate).
|
||||||
|
|
||||||
|
## ALTERNATIVE PATHS (pick one before building)
|
||||||
|
A. Shared-account section in the team tab (recommended above).
|
||||||
|
B. Close T-158 as substantially DONE by T-415 — the budget is already visible in
|
||||||
|
the Activity tab; a team-tab duplicate may be redundant. Q-34 answered either
|
||||||
|
way.
|
||||||
|
C. Keep the true per-member split as a FUTURE item, explicitly blocked on a
|
||||||
|
(currently non-existent) per-session multi-account login; park T-158 behind
|
||||||
|
it.
|
||||||
|
|
||||||
|
## ACCEPTANCE (for option A)
|
||||||
|
- Team tab shows ONE account-budget section (session / week / week-sonnet %)
|
||||||
|
when a `/usage` result is available, clearly labelled account-wide / shared.
|
||||||
|
- No per-member duplication of the same numbers.
|
||||||
|
- No new network/headless call beyond the existing user-initiated `/usage`
|
||||||
|
refresh (D-64 / POLICY: explicit user action only).
|
||||||
|
- en/nl parity for any new i18n keys.
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
T-415 (usage parse + Activity render); Q-34 (this resolves it); T-476 (per-REPO,
|
||||||
|
not per-session, account login — does NOT enable per-member budgets);
|
||||||
|
`parseUsageText` / `ClaudeUsage` (claude_status.dart); activity_tab.dart USAGE
|
||||||
|
block; team_tab.dart roster; claude_meta_sidebar.dart usage binding.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
DECISION 2026-06-26: go with option A.
|
||||||
|
|
||||||
|
Build the single shared-account budget section in the TEAM tab (account-wide,
|
||||||
|
clearly labelled — NOT per-member). Options B (close as done-by-T-415) and C
|
||||||
|
(per-member, deferred behind per-session multi-account) are declined: per-member
|
||||||
|
is not meaningful under one shared account, and the team tab should carry its
|
||||||
|
own budget view rather than send the user to the Activity tab.
|
||||||
|
|
||||||
|
Scope is now exactly the "RECOMMENDED SCOPE" above (team_tab.dart MetaSection +
|
||||||
|
thread _usage from claude_meta_sidebar.dart + team.section.usage i18n key,
|
||||||
|
en/nl). This resolves Q-34. Ready to pick up; not yet started.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────
|
||||||
|
REVISED 2026-06-27 (after live review): pivoted A → B.
|
||||||
|
|
||||||
|
Seeing the shared-account card on BOTH the Activity and Team tabs read as
|
||||||
|
redundant — usage is per-account and can''t be split per member, so one place
|
||||||
|
is enough. Removed the Team-tab account card (and its usage prop + i18n keys);
|
||||||
|
the budget now lives only on the Activity tab, next to the /usage refresh
|
||||||
|
control that fetches it (T-415). The three-tab card facelift stays. Q-34
|
||||||
|
answered: budget surfaces once, on Activity.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 20:48:38', '2026-06-27 05:55:56.468', NULL, 'c69aaa48a145eccdaed208eb01ab8a57', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
fleshed out to full token + syntax coverage and ships alongside a
|
fleshed out to full token + syntax coverage and ships alongside a
|
||||||
contrast-hardened `summer-night-hc`, both selectable in Settings → Appearance.
|
contrast-hardened `summer-night-hc`, both selectable in Settings → Appearance.
|
||||||
(T-478)
|
(T-478)
|
||||||
- **Account budget in the Team tab.** The Claude meta sidebar's Team tab now
|
|
||||||
shows the shared account `/usage` budget as a single labelled card (usage is
|
|
||||||
per-account, not per-member). (T-158)
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -194,8 +194,6 @@
|
|||||||
"taskRow.reassign": { "translation": "Reassign task" },
|
"taskRow.reassign": { "translation": "Reassign task" },
|
||||||
"team.empty": { "translation": "No team active." },
|
"team.empty": { "translation": "No team active." },
|
||||||
"team.section.tasks": { "translation": "TASKS" },
|
"team.section.tasks": { "translation": "TASKS" },
|
||||||
"team.section.usage": { "translation": "Account" },
|
|
||||||
"team.usage.shared": { "translation": "Shared across the team" },
|
|
||||||
"tabStrip.activity": { "translation": "Activity" },
|
"tabStrip.activity": { "translation": "Activity" },
|
||||||
"tabStrip.team": { "translation": "Team" },
|
"tabStrip.team": { "translation": "Team" },
|
||||||
"tabStrip.team.count": { "translation": "Team · {count}" },
|
"tabStrip.team.count": { "translation": "Team · {count}" },
|
||||||
|
|||||||
@@ -194,8 +194,6 @@
|
|||||||
"taskRow.reassign": { "translation": "Taak opnieuw toewijzen" },
|
"taskRow.reassign": { "translation": "Taak opnieuw toewijzen" },
|
||||||
"team.empty": { "translation": "Geen team actief." },
|
"team.empty": { "translation": "Geen team actief." },
|
||||||
"team.section.tasks": { "translation": "TAKEN" },
|
"team.section.tasks": { "translation": "TAKEN" },
|
||||||
"team.section.usage": { "translation": "Account" },
|
|
||||||
"team.usage.shared": { "translation": "Gedeeld met het hele team" },
|
|
||||||
"tabStrip.activity": { "translation": "Activiteit" },
|
"tabStrip.activity": { "translation": "Activiteit" },
|
||||||
"tabStrip.team": { "translation": "Team" },
|
"tabStrip.team": { "translation": "Team" },
|
||||||
"tabStrip.team.count": { "translation": "Team · {count}" },
|
"tabStrip.team.count": { "translation": "Team · {count}" },
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
/// `meta_sidebar/` (T-395 split). Activity and Config render on the same
|
/// `meta_sidebar/` (T-395 split). Activity and Config render on the same
|
||||||
/// table geometry (`buildMetaTable`) so switching tabs doesn't visually jump.
|
/// table geometry (`buildMetaTable`) so switching tabs doesn't visually jump.
|
||||||
///
|
///
|
||||||
/// The account budget now surfaces from a forwarded `/usage` (T-415): the
|
/// The account budget surfaces from a forwarded `/usage` (T-415): the Activity
|
||||||
/// Activity tab renders it, and the Team tab repeats it as a single shared
|
/// tab renders it next to its refresh control. It is NOT duplicated on the Team
|
||||||
/// ACCOUNT card (T-158) — usage is per-account (one `~/.claude` login), so it's
|
/// tab — usage is per-account (one `~/.claude` login), so it can't be split per
|
||||||
/// shown once, not split per member.
|
/// member; one place to see it is enough (T-158).
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@@ -313,7 +313,6 @@ class _ClaudeMetaSidebarState extends State<ClaudeMetaSidebar> {
|
|||||||
members: _members,
|
members: _members,
|
||||||
memberStatus: _memberStatus,
|
memberStatus: _memberStatus,
|
||||||
orchestrator: _orchestrator,
|
orchestrator: _orchestrator,
|
||||||
usage: _usage,
|
|
||||||
tasks: _tasks,
|
tasks: _tasks,
|
||||||
injectingAgentId: _injectingAgentId,
|
injectingAgentId: _injectingAgentId,
|
||||||
injectController: _injectCtl,
|
injectController: _injectCtl,
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
/// state, and orchestrator wiring. Split out of claude_meta_sidebar.dart
|
/// state, and orchestrator wiring. Split out of claude_meta_sidebar.dart
|
||||||
/// (T-395).
|
/// (T-395).
|
||||||
///
|
///
|
||||||
/// Also carries the shared account budget (T-158): the `/usage` figures are
|
/// The account `/usage` budget is deliberately NOT shown here: it is
|
||||||
/// per-ACCOUNT — every team session shares one `~/.claude` login, so this is a
|
/// per-account (every team session shares one `~/.claude` login), so it can't
|
||||||
/// single shared budget shown once and labelled as such, NOT a per-member split.
|
/// be split per member — it lives once on the Activity tab, next to the
|
||||||
|
/// refresh control that fetches it (T-158).
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:clide/builtin/claude/src/claude_status.dart' show ClaudeUsage;
|
|
||||||
import 'package:clide/builtin/claude/src/meta_sidebar/models.dart';
|
import 'package:clide/builtin/claude/src/meta_sidebar/models.dart';
|
||||||
import 'package:clide/builtin/claude/src/meta_sidebar/roster_row.dart';
|
import 'package:clide/builtin/claude/src/meta_sidebar/roster_row.dart';
|
||||||
import 'package:clide/builtin/claude/src/meta_sidebar/task_row.dart';
|
import 'package:clide/builtin/claude/src/meta_sidebar/task_row.dart';
|
||||||
@@ -36,7 +36,6 @@ class TeamTabView extends StatelessWidget {
|
|||||||
required this.onSetPermissionMode,
|
required this.onSetPermissionMode,
|
||||||
required this.onFork,
|
required this.onFork,
|
||||||
required this.onOpenChatPane,
|
required this.onOpenChatPane,
|
||||||
this.usage,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<TeamMemberJoined> members;
|
final List<TeamMemberJoined> members;
|
||||||
@@ -52,32 +51,27 @@ class TeamTabView extends StatelessWidget {
|
|||||||
final void Function(String memberName) onFork;
|
final void Function(String memberName) onFork;
|
||||||
final VoidCallback onOpenChatPane;
|
final VoidCallback onOpenChatPane;
|
||||||
|
|
||||||
/// Parsed `/usage` budget for the account this team shares (T-158). Null until
|
|
||||||
/// the first `/usage` refresh (driven from the Activity tab control).
|
|
||||||
final ClaudeUsage? usage;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tokens = ClideSettings.theme.of(context).surface;
|
final tokens = ClideSettings.theme.of(context).surface;
|
||||||
|
if (members.isEmpty) {
|
||||||
|
return metaPlaceholder(ClideSettings.i18n.string(context, 'team.empty', namespace: 'builtin.claude', placeholder: 'No team active.'));
|
||||||
|
}
|
||||||
final children = <Widget>[
|
final children = <Widget>[
|
||||||
..._accountSection(context, tokens),
|
for (final m in members)
|
||||||
if (members.isEmpty)
|
AgentRosterRow(
|
||||||
metaPlaceholder(ClideSettings.i18n.string(context, 'team.empty', namespace: 'builtin.claude', placeholder: 'No team active.'))
|
key: ValueKey(m.agentId),
|
||||||
else
|
member: m,
|
||||||
for (final m in members)
|
status: memberStatus[m.agentId],
|
||||||
AgentRosterRow(
|
orchestrator: orchestrator,
|
||||||
key: ValueKey(m.agentId),
|
injectingAgentId: injectingAgentId,
|
||||||
member: m,
|
injectController: injectController,
|
||||||
status: memberStatus[m.agentId],
|
onToggleInject: onToggleInject,
|
||||||
orchestrator: orchestrator,
|
onInjectSubmit: onInjectSubmit,
|
||||||
injectingAgentId: injectingAgentId,
|
onClose: onClose,
|
||||||
injectController: injectController,
|
onSetPermissionMode: onSetPermissionMode,
|
||||||
onToggleInject: onToggleInject,
|
onFork: onFork,
|
||||||
onInjectSubmit: onInjectSubmit,
|
),
|
||||||
onClose: onClose,
|
|
||||||
onSetPermissionMode: onSetPermissionMode,
|
|
||||||
onFork: onFork,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (tasks.isNotEmpty) {
|
if (tasks.isNotEmpty) {
|
||||||
@@ -96,43 +90,6 @@ class TeamTabView extends StatelessWidget {
|
|||||||
return ListView(padding: const EdgeInsets.all(12), children: children);
|
return ListView(padding: const EdgeInsets.all(12), children: children);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shared account-budget card (T-158): one ACCOUNT section with the three
|
|
||||||
/// `/usage` figures and a caption that it is account-wide, not per-member.
|
|
||||||
/// Empty when no `/usage` result has arrived yet.
|
|
||||||
List<Widget> _accountSection(BuildContext context, SurfaceTokens tokens) {
|
|
||||||
final u = usage;
|
|
||||||
if (u == null || (u.session == null && u.week == null && u.weekSonnet == null)) return const [];
|
|
||||||
return [
|
|
||||||
metaSectionHeader(context, tokens, ClideSettings.i18n.string(context, 'team.section.usage', namespace: 'builtin.claude', placeholder: 'ACCOUNT')),
|
|
||||||
metaCard(tokens, [
|
|
||||||
if (u.session != null)
|
|
||||||
metaCardRow(
|
|
||||||
tokens,
|
|
||||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.session', namespace: 'builtin.claude', placeholder: 'session'), u.session!),
|
|
||||||
),
|
|
||||||
if (u.week != null)
|
|
||||||
metaCardRow(
|
|
||||||
tokens,
|
|
||||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.weekAll', namespace: 'builtin.claude', placeholder: 'week (all)'), u.week!),
|
|
||||||
),
|
|
||||||
if (u.weekSonnet != null)
|
|
||||||
metaCardRow(
|
|
||||||
tokens,
|
|
||||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.weekSonnet', namespace: 'builtin.claude', placeholder: 'week (sonnet)'), u.weekSonnet!),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 2, top: 4),
|
|
||||||
child: ClideText(
|
|
||||||
ClideSettings.i18n.string(context, 'team.usage.shared', namespace: 'builtin.claude', placeholder: 'Shared across the team'),
|
|
||||||
muted: true,
|
|
||||||
fontSize: clideFontCaption,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _taskSection(BuildContext context, SurfaceTokens tokens) {
|
Widget _taskSection(BuildContext context, SurfaceTokens tokens) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/// T-158: the Team tab carries the shared account budget — a single ACCOUNT
|
|
||||||
/// card from the forwarded `/usage`, NOT a per-member split (usage is
|
|
||||||
/// per-account: every team session shares one ~/.claude login).
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'package:clide/builtin/claude/src/claude_status.dart' show ClaudeUsage;
|
|
||||||
import 'package:clide/builtin/claude/src/meta_sidebar/team_tab.dart';
|
|
||||||
import 'package:clide/widgets/widgets.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import '../../helpers/kernel_fixture.dart';
|
|
||||||
import '../../helpers/widget_harness.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
late KernelFixture f;
|
|
||||||
late TextEditingController inject;
|
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
f = await KernelFixture.create();
|
|
||||||
inject = TextEditingController();
|
|
||||||
});
|
|
||||||
tearDown(() {
|
|
||||||
inject.dispose();
|
|
||||||
f.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
// No team members + no catalog seeded, so labels fall back to their English
|
|
||||||
// placeholders ('ACCOUNT' is the uppercased section header).
|
|
||||||
TeamTabView view({ClaudeUsage? usage}) => TeamTabView(
|
|
||||||
members: const [],
|
|
||||||
memberStatus: const {},
|
|
||||||
orchestrator: null,
|
|
||||||
tasks: const [],
|
|
||||||
injectingAgentId: null,
|
|
||||||
injectController: inject,
|
|
||||||
onToggleInject: (_) {},
|
|
||||||
onInjectSubmit: (_, _) {},
|
|
||||||
onClose: (_) {},
|
|
||||||
onSetPermissionMode: (_, _) {},
|
|
||||||
onFork: (_) {},
|
|
||||||
onOpenChatPane: () {},
|
|
||||||
usage: usage,
|
|
||||||
);
|
|
||||||
|
|
||||||
testWidgets('renders the shared ACCOUNT budget card when usage is present', (tester) async {
|
|
||||||
await tester.pumpWidget(harness(f, view(usage: const ClaudeUsage(session: '15% used', week: '53% used', weekSonnet: '0% used'))));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(find.text('ACCOUNT'), findsOneWidget);
|
|
||||||
expect(find.text('15% used'), findsOneWidget);
|
|
||||||
expect(find.text('53% used'), findsOneWidget);
|
|
||||||
expect(find.text('0% used'), findsOneWidget);
|
|
||||||
// Labelled shared, not per-member.
|
|
||||||
expect(find.text('Shared across the team'), findsOneWidget);
|
|
||||||
// Solo (no members) still shows the budget above the empty-team notice.
|
|
||||||
expect(find.text('No team active.'), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('omits the ACCOUNT card when no usage has been fetched', (tester) async {
|
|
||||||
await tester.pumpWidget(harness(f, view()));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(find.text('ACCOUNT'), findsNothing);
|
|
||||||
expect(find.text('Shared across the team'), findsNothing);
|
|
||||||
expect(find.text('No team active.'), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('omits the ACCOUNT card when the usage result is empty', (tester) async {
|
|
||||||
await tester.pumpWidget(harness(f, view(usage: const ClaudeUsage())));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(find.text('ACCOUNT'), findsNothing);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user