Commit Graph
212 Commits
Author SHA1 Message Date
a5d8c6bd5b feat(video): CEO can preview a video authoring task's frames before approving (#608)
A source=video authoring task reaches awaiting_ceo_approval with no MP4
yet — rendering only happens after it completes — so the CEO had nothing
to review. Two CEO-gated routes now serve the request_render preview
frames: GET /video/preview-frames/{task_id} lists them per orientation
(parsed from the self-describing .previews/{task8}/{orientation}/ filenames
rather than the render_preview marker, which only holds the last call's
single orientation), and .../{orientation}/{filename} streams a frame's
PNG behind the existing path-confinement guard. The task-detail Overview
gains a Video preview card — a 9:16/1:1 toggle + prev/next/scrubber frame
stepper with composition id, duration, and a dirty badge — shown for a
video task with preview frames or awaiting CEO approval.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-20 10:51:43 +02:00
RoboCo Release Manager 31418c9a32 chore(release): 0.26.0 2026-07-20 05:19:54 +00:00
4a8050bf43 fix(panel): the /tg surface is exempt from the global 401→login redirect (#600)
The app-level agent-roster sync fires /api/agents on every surface; on
/tg it races the initData sign-in, takes the cloud-auth 401, and the
interceptor bounced the Telegram webview to the password /login page it
cannot complete — hijacking the cockpit into the dashboard. The Mini App
owns its auth UX (initData sign-in + its own wall), so the redirect now
exempts /tg via an exported, tested path predicate.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-20 02:01:10 +02:00
bab53e31ca perf(panel): kanban virtualization + row memoization + scorecards batch endpoint (#594)
* perf(panel): kanban virtualization + row memoization + scorecards batch endpoint

The audit's remaining phases: the kanban card lists render through
@tanstack/react-virtual windows (columns are the dnd drop targets, cards
only drag — no sortable conflict) with memoized columns/cards and a
stabilized handleAction; the task table's desktop row and mobile card are
extracted and memoized (the table itself already client-paginates to 100).
Backend: the per-member scorecard N+1 (~20 requests x 3 queries per poll)
collapses into GET /dashboard/metrics/members backed by
get_all_member_scorecards with grouped rollup/overlay SQL shared with the
single-agent path.

* test(metrics): shared-DB-safe scorecard tests — unique seeds, delta assertions

The two new batch-scorecard tests assumed a private DB: fixed ceo/system
slugs collided with other tests' seeds (ix_agents_slug) and a global
exactly-one CEO lookup + exact roster count broke in the one-process
suite. Unique slugs, subset/disjoint assertions, dead count constant
dropped.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 19:42:24 +02:00
700dbcd285 feat(tg): Mini App V5 — brand typography, icon depth, motion, detail sheets (#583)
* feat(tg): Mini App V5 — brand typography, icon depth, motion, detail sheets

Share Tech Mono (the vendored motion-brand face) becomes the cockpit's
display voice via next/font/local scoped to #tg-shell; icon tiles, circle
actions, and avatars get gradient/ring depth; a dependency-free motion
vocabulary lands (spend count-up, tab rise-in, staggered sections,
sparkline draw-in, sheet slide-up with native BackButton dismiss); the
Board tab gains a tap-through task sheet (ACs, open findings, PR link),
Today's fleet opens a full-roster sheet, and Board/Inbox join the
/tg?demo=1 fixtures.

* feat(tg): custom RoboCo icon set + operations ring

The cockpit stops using stock lucide on its hero surfaces: a hand-drawn
duotone icon set (speedometer, seal, bell, kanban, brand-cursor bubble,
rocket, double-check, broom, robot head) covers the tab bar and the Today
ring. The ring itself stops duplicating the tab bar and becomes real
operations: Ship deep-focuses the release proposal in Approvals, Ack all
bulk-acknowledges pending notifications, Sweep runs the stale-branch
cleanup across every git-configured project behind a confirm sheet, and
Fleet opens the roster.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 18:59:54 +02:00
c7605b0d77 feat(tg): premium Mini App cockpit — spend hero, charts, avatars (#582)
* feat(tg): premium Today — spend hero, trend, quick actions, fleet avatars

The cockpit home stops being flat cards and becomes a real app surface:

- Spend HERO: the day's cost at 40px with a signed delta-vs-yesterday
  chip and a live 7-day amber area sparkline (hand-rolled inline SVG, no
  charting lib in the Mini App bundle).
- Quick-action ring: circular Approve (amber + needs-you badge) / Board /
  Inbox / Chat, the wallet-style primary-verb row.
- Needs-you as a rich amber gradient banner (top items + draft chips)
  instead of a plain section.
- Fleet as live avatar tokens (stable per-name hue, pulse dot) over the
  working list.
- "Shipped this week" day-bars (today emphasized) + week total.

Backend: /telegram/today gains spend.series (7-day cost) + delta_pct and
a velocity series (per-day completed tasks) — two cheap grouped-by-day
queries, same DB-only ethos, degrading to zeros on error.

* feat(tg): color-code approval rows by kind

TgRowIcon gains a tone prop; the approvals list tints each tile per kind
(amber Release / sky X post / violet Video / emerald Roadmap) so a mixed
queue reads as color-coded instead of a monochrome column.

* feat(tg): sender/peer avatars on Inbox + Chat

Inbox notifications and Chat conversation rows adopt the fleet-avatar
language: a per-name-hued initials token leads each card, unread inbox
items carry a subtle primary tint, and both cards move to the rounded-2xl
surface — so every tab now shares one visual system. Board keeps the
shared MobileTaskBoard (already grouped/pill-styled, and reused outside
the cockpit).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 11:51:06 +02:00
Renn F a072b980bc fix(tg): show Open-from-Telegram wall for empty initData in production
Opening /tg in a plain browser loads telegram.org's script, which
defines window.Telegram.WebApp with EMPTY initData (no real launch
behind it). Production posted that empty string to webapp-auth → 422 →
"Couldn't sign in". The dev path already guarded this (cb55f2b9); the
prod path didn't. Now a bridge with no initData that isn't the dev mock
shows the "Open from Telegram" wall instead of erroring — the cockpit is
a phone-from-Telegram surface, and a desktop browser gets the wall, not
a failed auth.
2026-07-19 10:48:05 +02:00
96401f4c10 feat(forge): Phases 2+2.1+3 — Gitea + GitLab providers, per-call routing, local-merge fallback (#575)
* feat(forge): Phase 2 — Gitea provider, per-call routing, host registry

Gitea support lands behind the Phase-1 seam:

- GiteaProvider (services/forge/gitea.py): Gitea v1 transport addressed
  by instance host (api base from the project's git_url). Where Gitea's
  wire contract diverges from GitHub's, the provider adapts responses
  back into the shapes GitService already classifies (ShapedResponse):
  `token` auth scheme, duplicate-PR 409→422 with the "already exists"
  text GitService keys on, commit statuses reshaped into check_runs /
  workflow_runs envelopes, APPROVE→APPROVED review mapping, Do-keyed
  POST merge, merge-method repo keys, label-color '#' prefix,
  client-side head/base PR filtering. Deliberate postures per the spec:
  zero-workflows fail-open (statuses-free repo → no_ci_configured) and
  merge_branch as a shaped 501 (env-sync cascade lands on missing_ref;
  the shared local-git fallback is Phase-2.1).
- ForgeRouter (services/forge/router.py): GitService._forge now routes
  per call from RepoRef.host — every existing call site unchanged in
  shape. RepoRef gains an optional host; _parse_git_url returns the
  host-stamped ref and it is threaded through GitService/release
  executor instead of being rebuilt from strings (helpers re-signatured
  to take RepoRef).
- Host registry (services/forge/registry.py): in-memory host→provider
  map, self-healing — ProjectService.get/get_by_slug re-register on
  every read; provider_for resolves gitea projects by git_url host.
- Registration validation now accepts git_provider="gitea"; GitLab
  remains recognized-but-rejected. Panel: the read-only Forge badge
  becomes a real picker (Auto-detect / GitHub-GHE / Gitea / GitLab
  disabled).

Plain git (clone/fetch/push) needs no changes — the Basic-auth
extraheader works on Gitea unchanged. Gates: mypy 392 files, xenon A,
full unit suite 6356 green, integration suite 2257 green.

* feat(forge): live-Gitea contract suite + scheme support + slash-safe refs

Hardening from running the provider against a real dockerized Gitea
1.22.6 (the spec's Phase-2 contract suite, now committed as the
env-gated tests/e2e_smoke/test_gitea_live.py — self-seeding: creates its
own repo, pushes real commits, and drives PR open → duplicate reshape →
list/filter → diff → review → labels → commit-status CI reshapes →
squash merge → branch delete → release, plus a live verification of the
x-access-token Basic-auth git-CLI claim).

Two real findings fixed:
- Branch refs weren't URL-encoded — every RoboCo branch carries slashes
  (feature/backend/...), and Gitea's router 404s on the extra path
  segments. list_ci_runs + delete_branch_ref now quote the ref
  (regression-pinned in the unit suite).
- The API base hardcoded https; a LAN instance serving plain http is a
  real deployment shape. GiteaProvider gains a scheme (recorded per host
  by the registry from the project's git_url).

ShapedResponse moves to forge/shaping.py (shared by the upcoming GitLab
transport, which needs its text override for diff reassembly).

* feat(forge): Phase 3 GitLab provider + Phase 2.1 local-merge fallback

GitLabProvider (services/forge/gitlab.py): GitLab v4 transport addressed
by host+scheme, subgroup-safe (the MR project path packs into
RepoRef.owner, URL-encoded per call). Adapters translate MR semantics
into the GitHub shapes GitService classifies: iid→number,
source/target_branch→head/base with a merged bool, per-file diffs
reassembled into unified-diff text (ShapedResponse text override,
3-page cap), approve-vs-note review routing (GitLab has no
request-changes verb), pipelines/statuses reshaped into
workflow_runs/check_runs, merge-method repo-key mapping, duplicate-MR
409→422. Reviewer mirroring is skipped (needs numeric ids RoboCo
doesn't store); provisioning stays Phase 4. gitlab.com now auto-detects
at registration like github.com; self-hosted GitLab sets the provider
explicitly (panel picker enabled).

Phase 2.1: neither Gitea nor GitLab has GitHub's server-side merges
API — their merge_branch returns a shaped 501 and
GitService.sync_env_branch now runs the shared local-git fallback
(_local_merge_branch: throwaway clone → ancestor check → merge → push;
a conflict aborts with the remote untouched; same status vocabulary as
the merges-API path).

Also aligns the whole tree with the full gate's tests/-scoped mypy
(provider-test responder typing, e2e_smoke's stale owner/repo shapes).
Gates: mypy 1229 files clean, xenon A, unit suite 6393 green, forge
suites 85 green, panel typecheck/lint clean.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 08:12:34 +02:00
baa87d584a feat(tg): Mini App V4 — Today brief, native approvals, live data, bot tier, chat bridges (#576)
* feat(tg): P0 — dev mock bridge + Telegram-native foundations

Mini App V4 phase 0. The (tg) shell gains the groundwork every later
phase builds on:

- Dev mock bridge: outside Telegram, a development build falls back to a
  no-op WebApp object and skips the webapp-auth POST (the regular panel
  session cookie authorizes API calls), so the cockpit is workable in a
  plain browser. Production keeps the "Open from Telegram" wall.
- Telegram theme adoption: themeParams map onto the shadcn CSS variables
  scoped to #tg-shell (desktop dashboard untouched), colorScheme drives
  the dark class, themeChanged re-applies live. Non-hex values are
  dropped at the trust boundary.
- Viewport/swipe correctness: shell height rides Telegram's own
  --tg-viewport-stable-height (100dvh fallback), vertical swipe-to-close
  disabled so list scrolling can't dismiss the app.
- Native chrome bindings: TgWebAppProvider context plus useMainButton /
  useBackButton declarative hooks and a null-safe haptics helper —
  consumers never touch window.Telegram directly.

* feat(tg): P1 — Today home tab + one-round-trip /telegram/today brief

Mini App V4 phase 1: the cockpit now opens on a "Today" brief answering
"does anything need me?" in one glance.

Backend: GET /api/telegram/today (CEO-gated, rate-limited) returns the
whole brief in one round trip via the new TgCockpitService — needs-you
items (awaiting-CEO + blocked tasks capped for a phone screen, held-draft
counts across release/X/video/roadmap queues), a fleet snapshot with
per-agent current-task titles, today's spend from the day rollup
(degrading to zeros on a usage hiccup, mirroring the CEO overview), and
ship state. Deliberately DB-only: no live GitHub calls, no readiness
snapshot (that path clones), no orchestrator singleton — the CI
red/green proxy is the set of open ci_watch fix tasks.

Panel: TgTodayTab is the new default tab (Gauge icon) — needs-you rows
and draft chips deep-link into the tab that acts on them (with a haptic
tap), fleet/spend/ship render as dense cards, 45s refetch until the P3
WebSocket wiring lands.

* fix(tg): dev mock engages when the CDN bridge loads outside Telegram

Live browser smoke caught it: a bare tab still loads telegram-web-app.js,
so window.Telegram.WebApp EXISTS outside Telegram — just with empty
initData. The dev fallback keyed on a null bridge only, so a dev browser
went down the real-auth path and posted empty initData instead of
mounting the mock. The fallback now treats bridge-with-no-initData the
same as no bridge (a real Telegram launch always carries initData);
production behavior is unchanged.

* feat(tg): P2 — native approvals card stack

Mini App V4 phase 2: the Approvals tab stops stacking the four desktop
queue cards and becomes a phone-native flow — one normalized list across
release proposal / X drafts / video drafts / proposed roadmap items, and
a full-context detail per item:

- Release: version/bump/gate badges, changelog draft, gaps, migration
  notes, in-flight + failed-execute banners; approve runs the fail-closed
  executor, reject requires a substantive change request (10 chars).
- X: editable body with the live 280 counter, replied-to mention quoted;
  approve sends the edited body only when actually edited.
- Video: cut-toggled player (blob-fetched through the authed client — a
  bare <video src> would 401), per-platform caption edits with 280/2200
  counters; approve sends only checked-in edits.
- Roadmap: the PO's full pitch (description, rationale, ACs); approve
  materializes into the backlog per item.

The detail's primary action rides Telegram's native MainButton and back
navigation rides the BackButton, with visible fallbacks outside Telegram
(dev mock, old clients). Haptics fire on outcomes. An acted-on item
vanishes from the refetched queue, popping back to the list by
construction. A failed queue source is surfaced ("list may be
incomplete" / "couldn't load") instead of masquerading as an empty
queue — caught live in the browser smoke.

* feat(tg): dev demo mode — /tg?demo=1 renders canned cockpit data

Development-only: with the flag param present, the Today brief and the
four approval queues resolve typed fixtures (dynamically imported, so
production bundles never carry them) instead of hitting the backend —
the cockpit is fully browsable with zero stack running. Mutations still
go to the real API and fail loudly; it's a showroom, not a simulator.

* feat(tg): P3 — cockpit rides /ws/system live

Chat adopts the desktop A2A invalidate-on-frame idiom over the shared
ref-counted /ws/system socket: every a2a.message frame refreshes the
conversation list and the affected thread, missed-frame gaps are healed
by a reconnect refetch, and the 10s thread poll turns off entirely while
the socket is up (it remains the fallback). The Today brief refreshes on
each USAGE_SNAPSHOT push so the spend line tracks the sweeper live, with
the 45s poll as the socket-down fallback. No new sockets, no backend
changes — the WS gate already accepts the cloud-auth session cookie.

* feat(tg): P4 — deterministic bot command tier + self-syncing menu

Mini App V4 phase 4 (deterministic half): three new bot commands beside
/status /queue /task —

- /agents: who's mid-task right now, from the same TgCockpitService
  fleet snapshot the Today brief renders (now public `fleet()`).
- /usage: today's spend from the day rollup.
- /blocked: awaiting-you + blocked tasks, deep-linked into the panel,
  capped per section, titles HTML-escaped.

BOT_COMMANDS is the single registry driving /help AND a once-per-process
Bot API setMyCommands sync on the first poll cycle (new client method,
best-effort), so the Telegram command menu can never drift from what the
code implements. The interactive tier (/secretary, /newtask riding a
live Intake interview in-thread) is specced but not in this commit.

* feat(tg): direction-C styling pass — Telegram palette, RoboCo voice

The cockpit stops wearing default-shadcn and gets its own visual
language on top of the P0 themeParams bridge (colors stay CSS-variable
driven, so inside Telegram everything still adopts the user's theme):

- Shared primitives (components/tg/ui.tsx): TgSection grouped cards with
  tracked micro-label headers, TgRow list rows (44px targets, press
  feedback, 1/2-line clamp), TgRowIcon glyph tiles, TgStat tabular-nums
  figures. Every tab composes the same three, so density and rhythm are
  identical across the surface.
- Shell renders a centered 430px column (sm:border-x) — the phone UI no
  longer stretches across a desktop dev browser.
- Tab bar: tighter type, active stroke-weight shift, backdrop blur.
- Today: needs-you count badge, divided task rows with inline blocked
  marker, fleet as mono-named rows, spend/ship as stat tiles.
- Approvals rows as icon-tile cards; detail header gains the kind glyph.
- Inbox/Chat rows aligned to the same card language.

* feat(tg): P5 — /secretary and /newtask live-chat bridges

The bot's interactive tier: both commands bridge the CEO's Telegram chat
into the same in-process runtimes the panel drives — the persistent
Secretary container and the scoped Intake interview.

There is no synchronous send→reply seam (replies land on the session's
single-consumer relay queue), so each bridged session runs one long-lived
consumer task (roboco/services/telegram_bridge.py) that drains
PrompterLiveRegistry.stream and pushes one Telegram message per completed
turn. While a session is live, plain chat text IS the conversation;
/end closes it.

/newtask resolves the intake scope (single project auto-picked, multiple
offered as a tap-to-pick keyboard holding the initial text), and the
interview happens in-thread. A draft proposal renders as a card with
Send-to-Board / Discard buttons: confirm routes through the normal
board-review path (PrompterService.confirm_live_draft, route=board) and
PARKS the session — board feedback later streams straight back into the
same thread, closing the redraft loop from the phone. MegaTask batches
still confirm in the panel only.

The consumer's open stream arms the registry's 60s keepalive, so the
bridge runs its own idle TTL (same setting, parked sessions exempt).
State is per-process in-memory by design (the _PENDING_REPLIES posture);
intake/secretary containers are process-wide singletons, so a bridged
session preempts a live panel session of the same kind by construction.

* feat(tg): cockpit skin — RoboCo dark deck with a constant amber accent

The cockpit no longer inherits the dashboard's white default outside
Telegram: #tg-shell carries its own standing skin (deep slate surfaces,
amber primary) so the Mini App looks like RoboCo everywhere. Inside
Telegram the themeParams bridge now overrides SURFACE tokens only —
background/card/text/hint/border repaint to the user's Telegram theme
while --primary/--ring stay RoboCo amber: Telegram's surfaces, RoboCo's
voice. Demo fixtures also rewritten to neutral content (they previously
depicted unbuilt forge work and already-shipped roadmap items as live).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 07:29:04 +02:00
461a6e1ae7 feat(forge): Phase 1 — GitProvider seam, GitHub transport extracted (#571)
* feat(forge): Phase 0 — git_provider column + registration-time forge validation

Pointing a project at a GitLab/Gitea git_url used to fail silently, several
steps deep, at first PR. New pure policy module (foundation/policy/forge.py)
detects the provider from the git_url host and validates at the
ProjectService create/update chokepoint: github auto-detects and
auto-stamps, explicit git_provider=github is the GitHub Enterprise escape
hatch, gitlab/gitea are recognized-but-not-yet-supported, unknown hosts get
a loud rejection with guidance. An update changing git_url does NOT inherit
a stored auto-stamped provider (restating the override is required), so a
host swap can't smuggle the escape hatch past validation. Migration 075
adds the nullable projects.git_provider column; the panel project dialogs
show the detected forge. Phase 0 of the forge-providers spec.

* feat(forge): Phase 1 — GitProvider seam, GitHub transport extracted

roboco/services/forge/: base.py holds the pure contracts (RepoRef +
GitProvider ABC, stdlib-only — a later GitLabProvider is implemented by
reading this file alone), github.py the httpx transport (20 endpoint
methods behind one shared request-plumbing helper set), registry.py the
wiring (git_provider column -> provider, failing loud on gitlab/gitea).
GitService keeps its exact public surface and all response
classification; its 26 inline REST call sites route through a lazy
_forge property (several suites build GitService via __new__, so an
__init__-set attribute breaks them). github_provisioning and
release_executor ride the same provider. Zero behavior change — the
pre-existing suites pass unmodified; per-project provider resolution
lands with the second provider.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 19:11:56 +02:00
7e01c0cecf feat(marketing): project-branded drafts + project badges on the X/video queues (#570)
Item B+C of the video/X per-project targeting spec, plus the
company_goals.company_name field they depend on (migration 075).

- CompanyGoalsService.resolve_product_name is the single fallback chain
  (project name -> charter company_name -> RoboCo); XEngine and
  VideoEngine both call it and their prompt builders are pure functions
  taking product_name — release posts/videos stop hardcoding RoboCo.
- The X and video queue responses carry project_slug/project_name via one
  shared unloaded-guard helper (api/schemas/project_fields.py); both
  panel queues render a shared ProjectBadge so multi-project drafts are
  tellable apart.
- Business -> Goals editor gains the company-name input.
- Fixes a pre-existing test-isolation leak: the company-goals routes test
  commits the charter singleton into the session-scoped test DB and
  polluted later suites; it now deletes the row on teardown.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 19:11:03 +02:00
388bab2488 feat(forge): Phase 0 — git_provider column + registration-time forge validation (#569)
* feat(forge): Phase 0 — git_provider column + registration-time forge validation

Pointing a project at a GitLab/Gitea git_url used to fail silently, several
steps deep, at first PR. New pure policy module (foundation/policy/forge.py)
detects the provider from the git_url host and validates at the
ProjectService create/update chokepoint: github auto-detects and
auto-stamps, explicit git_provider=github is the GitHub Enterprise escape
hatch, gitlab/gitea are recognized-but-not-yet-supported, unknown hosts get
a loud rejection with guidance. An update changing git_url does NOT inherit
a stored auto-stamped provider (restating the override is required), so a
host swap can't smuggle the escape hatch past validation. Migration 075
adds the nullable projects.git_provider column; the panel project dialogs
show the detected forge. Phase 0 of the forge-providers spec.

* fix(panel): mock-mode forge detection extracts the real host

CodeQL js/incomplete-url-substring-sanitization: the substring check
matched github.com anywhere in the URL. Extract the hostname (URL parse
or scp-form regex, mirroring forge.py) and require an exact match.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 19:10:43 +02:00
Renn F fc6d6f6458 fix(a2a): CEO pairs join the switchboard matrix; sections collapsible
_SWITCHBOARD_SLUGS reused is_human_only_role (spawn semantics) and dropped
the CEO before can_a2a_direct — which allows CEO -> anyone — ever ran, so
the static pair matrix had no CEO pairs and a Renzo filter emptied the
switchboard. Only prompter/secretary/system are excluded now; CEO pairs
get their own 'CEO Direct' section (matrix 70 -> 93). Every switchboard
section header is now a collapse toggle (Radix Collapsible, default open).
2026-07-18 15:55:40 +02:00
f0782cb858 chore(panel): selective dependency updates — Next family held at the 16.1.x pin (#560)
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 08:12:43 +02:00
713f91320a feat: Journals joins the Agents hub — third tab (#559)
* feat(panel): Journals joins the Agents hub as its third tab

* docs(map): Journals tab on the Agents hub; registry retargets

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 07:52:28 +02:00
4480dfe8d1 feat: Agents hub — Fleet + Conversations tabs, /a2a merged in, DM quick-action (#558)
* feat(panel): Agents hub — Fleet and Conversations tabs, /a2a redirect, DM quick-action

* fix(panel): validate deep-linked DM targets against roster and exclusions; re-arm the dm latch

* docs(map): panel entries for this wave

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 07:06:32 +02:00
1a726d4c34 feat: customizable Quick Actions on the Overview dashboard (#557)
* feat(panel): customizable Quick Actions on Overview — registry, defaults, persisted picker

* fix(panel): legacy bar actions join the quick-action defaults; empty state; persist-contract test

* docs(map): panel entries for this wave

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 07:05:20 +02:00
02601fef22 feat: Workstation card grids — view toggle, sorting, agent-card visual language (#556)
* feat(panel): Workstation card grids with persisted view toggle and sorting

* fix(panel): stable multiplier sort with cell-label fallback after adversarial review

* docs(map): panel entries for this wave

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 07:04:38 +02:00
Renn F 29b375f1b5 fix(panel): session links target the owning task — /work-sessions/<id> never existed 2026-07-18 05:41:07 +02:00
c40a7a39c3 feat: Telegram V3 — Mini App cockpit (initData auth + /tg surface) (#554)
* feat(telegram): Mini App auth — initData validation mints the cloud-auth session cookie

* feat(panel): /tg Mini App cockpit — approvals, inbox, read-only board, A2A chat

* fix(telegram,panel): unconditional webapp-auth rate limit, future-dated initData rejection, anchored /tg matcher

* docs(map,rag): Telegram Mini App auth route, initData validator, (tg) surface

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 02:47:59 +02:00
9fbec78126 feat: Telegram V2 — inbound commands + actionable approve/reject from chat (#551)
* feat(telegram): V2 inbound — command router, actionable approve/reject keyboards, chat-gated poll loop

* fix(release,x,video,telegram): terminal-state guards on approve/reject; sender-identity check

* docs(map,rag): Telegram V2 inbound surfaces and terminal-state approve/reject guards

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 00:47:09 +02:00
c5dfbf6063 feat: Workstation page — Products + Projects unified behind one sidebar entry (#549)
* feat(panel): Workstation page — Products and Projects as tabs behind one sidebar entry

* docs(map): workstation page, extracted views, and the local-filter scroll trade-off

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 00:45:21 +02:00
496c24d186 feat: git hygiene (branch/preview reaping + cleanup sweep) and panel charts; work sessions under Git (#548)
* feat(panel): session-start, 7d overview spend, and 30d business spend charts

* feat(panel): surface work sessions as a Git page tab (route was orphaned)

* feat(git): reap spent task branches and render previews at lifecycle chokepoints; guarded stale-branch sweep

* feat(panel): stale-branch cleanup button on the Git page

* fix(git,panel): cursor-resumable sweep, force-delete spent refs, local filter state

* docs(map,rag): branch/preview reaping, cleanup sweep, git-tab work sessions, wave-2 charts

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 00:44:48 +02:00
885d6bbe83 feat: CEO-grade A2A — New DM composer, CEO-DM wake, docs scrub (#547)
* feat(panel): CEO New-DM composer and direct-thread replies on the A2A page

* docs(agents): remove dm-the-CEO teaching; fix Board/HoM dead-end escalation recipes

* feat(a2a): CEO-authored DMs wake offline recipients via the a2a_request dispatch path

* fix(a2a,panel): wake only read_a2a-capable roles; case-insensitive header defaults; wider DM picker exclusions

* docs(map): CEO-DM wake mechanics, requires_ack override, A2A composer components; comms-model update

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 00:44:00 +02:00
9b4ce6b9c8 fix: wave 1 quick wins — agent names, scroll bounce-back, chart empty states, model-pin preservation, UUID spawn normalization (#546)
* fix(panel): notifications show agent names, metrics charts get empty states

* fix(panel): stop expand/collapse scroll bounce-back; add floating scroll-jump buttons

* fix(llm): provider mode switches preserve per-agent model pins

* fix(api): normalize agent UUID to slug at the orchestrator route boundary

* fix(panel,docs): align routing-card copy and map docs with preserved-pin mode switches

* fix(panel): drop dead unfiltered scroll hook, re-observe on Suspense swap, name system sender

* docs(map): reflect preserved-pin mode switches, UUID-slug normalization, panel wave-1 deltas

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-18 00:42:13 +02:00
Renn F 0843bbaaaa fix(panel,video): dead dialog triggers behind tooltips; dotted composition ids render
HelpTip nested inside a Dialog/AlertDialog trigger puts the trigger's
click handler on the Tooltip root, which renders no DOM — the agents
Spawn item and the KB Reindex-All / Delete-index confirms were dead.
Tooltips now wrap the triggers. The video renderer accepts interior
single dots in composition ids (release-0.25.0) with '..' still
unrepresentable, and propose_video refuses an unrenderable id at
authoring time.
2026-07-16 15:47:08 +02:00
Renn F 33cb4d7104 fix(release): CI wait polls the prod rung; escape the header tooltip apostrophe
get_latest_ci_conclusion defaults to the ladder's head rung, so
wait_for_ci searched slave for a release commit that lives on master
and timed out after 40 minutes with the run already green. The wait
now passes the prod branch explicitly. Also fixes the
react/no-unescaped-entities error that turned master's Panel CI red.
2026-07-16 06:59:22 +02:00
RoboCo Release Manager 3e8b3d82e6 chore(release): 0.25.0 2026-07-16 04:03:09 +00:00
Renn F e939d73844 fix(panel): hide the Report-CEO button when no handler is wired
The prop was never passed by any consumer, leaving a silent no-op
button on urgent flags; it now renders only when a real handler
exists.
2026-07-15 18:05:58 +02:00
Renn F dcd2f84e69 feat(panel): dense tooltip pass — work surfaces (270 tips)
Task lifecycle actions get a 25-action tip map, every dialog field and
git panel control annotated; fixes two round-1 disabled-button tips
that could never fire and a double data-state stamp on tab-progress.
2026-07-15 18:04:00 +02:00
Renn F f9dbb30a4e feat(panel): dense tooltip pass — org surfaces + layout chrome (253 tips)
Every sidebar destination, metric derivation, sortable column, queue
action, and dialog control explains itself; mobile tab bar shares the
sidebar's descriptions so the two nav surfaces can't drift.
2026-07-15 18:04:00 +02:00
Renn F e7d8e7ecb5 fix(panel): AI routing breathing room — wider column gutters, roomier override rows
Key band gets a 14-unit gutter between the key column and self-hosted;
mix-mode rows gain horizontal separation, vertical rhythm, and taller
row padding.
2026-07-15 17:57:42 +02:00
Renn F 57254b3ce2 feat(panel): dense tooltip pass — projects, products, social, KB, business, settings, notifications (178 tips)
Density bar: every dialog field, column header, filter, badge, and
status pill explains itself — grounded in the backend (ladder
normalization, rate-limit park/probe, self-hosted fallback, reindex
scope). Disabled buttons span-wrap so tips fire; tabs re-assert
data-state.
2026-07-15 17:56:12 +02:00
Renn F 84563e84b6 feat(panel): worked-example tooltip on the environment ladder
The section label now explains the typical dev/slave -> prod/master
two-rung setup inline, when to add middle rungs, and what no ladder
means.
2026-07-15 17:31:45 +02:00
Renn F 5fefb0f2c0 fix(panel): environment ladder speaks promotion flow, not git jargon
First rung badges 'PRs land', last badges 'release', middles drop the
fake rung-N names, and a 'promotes to' connector shows the direction
between rows; helper copy and the dialogs' default-branch hint follow
the same vocabulary.
2026-07-15 17:30:35 +02:00
Renn F 77229b10b1 fix(panel): agent cards — intrinsic auto-fill grid, roomier cards
repeat(auto-fill, minmax(17rem, 1fr)) replaces the breakpoint ladder:
one column on a phone through eight on a 27", consistent card width
across every team section at any viewport. Cards gain padding and a
base-size title.
2026-07-15 17:17:19 +02:00
Renn F 3d1c20e95a feat(panel): tooltip sweep — overview, agents, A2A, journals, auditor, metrics
Derivation tips on every key-metric, scorecard, and quality-metrics
figure; the cryptic member-scorecard headers get full decodes; avatar
initials, truncated ids, and toggle buttons gain accessible names;
title-only hints upgrade to the HelpTip idiom throughout.
2026-07-15 16:40:13 +02:00
Renn F c37516f640 feat(panel): tooltip sweep — projects, products, social, KB, business, settings, notifications
All 34 feature flags get verified one-line tips; secret inputs state
the write-only contract; KB index types get canonical descriptions;
switch tips ride Labels so Radix data-state stays intact. Also fixes
the scorecard SectionLabel swallowing props, which made tooltips on it
silently inert.
2026-07-15 16:33:22 +02:00
Renn F d1b02e3747 feat(panel): tooltip sweep — tasks, kanban, task assistant, git, work sessions (58 tips, 22 files)
Full import-graph walk per page: filter-chip and plan-section icon
buttons gain their first accessible names, per-state description maps
for git/docs/finding/priority badges, truncation-gated full-value tips,
disabled buttons explain their gate. Stateful Radix triggers use the
task-tabs data-state re-assertion pattern.
2026-07-15 16:30:26 +02:00
Renn F 14ee9b3b49 fix(panel): phantom agent states in dashboard + metrics counters
by_state.running / a.state 'running'/'stopped' never exist in the
orchestrator enum (offline/starting/active/waiting/idle/stopping), so
these counters were structurally zero — same class as the agents-page
Active bug, remaining two sites.
2026-07-15 16:07:55 +02:00
Renn F 9b0a8b0fae fix(panel): agents page — truthful counts, merged stat row, dense org grid
Total Agents now counts the roster (backend total_agents is live
containers only) and Active reads by_state.active — running/ready never
existed in the backend enum, so the counter was structurally zero.
Board + Main PM fold into one leadership band, cards compress to a
status dot + single detail line, and the grid densifies with per-team
count badges.
2026-07-15 16:06:38 +02:00
Renn F 2efc2f9542 fix(panel): compact task-status tiles beside the donut; journal ids copyable + task-linked
Five near-empty stat cards become a 2x3 tile band sharing the row with
the status donut. Journal entry cards and the entry page render full
ids with the shared CopyButton and a /tasks quick-link badge, with the
entry-card anchor restructured so links no longer nest.
2026-07-15 16:03:11 +02:00
Renn F 614c22ed70 fix(panel): AI providers layout — org-grouped two-column override list; key cards beside self-hosted
Per-agent mix rows group under Board / Main PM / cell / intake headers
in a two-column grid with compact selects; Grok + Ollama key cards
stack in the left column with the self-hosted section to their right.
Save-mix and inherit semantics untouched.
2026-07-15 16:01:06 +02:00
Renn F 6dee7e66a0 fix(panel): task-detail active tab highlight — tooltip wrapper clobbered Radix Tabs data-state
TooltipTrigger asChild spreads its own data-state (closed) through the
Slot merge onto the TabsTrigger, overriding Tabs' active/inactive
attribute, so no tab ever matched the data-[state=active] styles.
Re-asserting data-state in the trigger's own props survives the merge;
derived from the same activeTab that drives the controlled Tabs.
2026-07-15 16:00:18 +02:00
Renn F fb02ae726f fix(panel): agent detail survives a stopped agent — fatal card only on a real roster miss
GET /orchestrator/agents/{id} 404s for any non-running container, and
the page's error early-return unmounted the DB-backed header/activity
content #529 had placed behind it. The fatal card now gates on the
roster identity lookup; a live-status error degrades in place to a
not-running banner + spawn dialog, with retry disabled on the
deterministic 404.
2026-07-15 15:58:29 +02:00
Renn F 59ff610805 fix(panel): overview reorder — quick actions + key cards first, team health second, queues last; PR Reviews + Social share a row
Removes the mobile order hack that hoisted decision queues above the
fold; the empty PR-Reviews state drops to the compact house convention.
2026-07-15 15:58:11 +02:00
be553ee9dd [w8b] Fix release-proposal flow: reject frees dedup, surface execute outcome (#525)
Reject cancelled the proposal's status but never moved it out of the
held-proposal set, so the one-open-proposal dedup blocked the release
manager from ever re-assessing — a rejected proposal deadlocked the cycle.
reject() now sets CANCELLED (mirroring video_post_service), which
list_open_release_proposals already excludes, so a fresh proposal can
originate next cycle.

A failed ~40min background execute (gate red, CI red, or an unexpected
crash) left the proposal silently PENDING with no signal to the CEO.
_run_approve_background now writes a release_execute_outcome marker
(status + detail) on every terminal outcome, and an 'error' marker on an
unhandled exception. GET /proposal surfaces execute_status / execute_detail
/ execute_in_flight (derived from the in-memory _INFLIGHT_APPROVES registry)
so the panel can show a running badge, a failure block with the reason, and
a Retry-approve label instead of a silent wait.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-15 06:10:31 +02:00
bb3b4b0c6d W6: Telegram notifications bridge (V1) (#524)
* feat(gateway): reviewer/PM collision map (W5)

The collision surface (intends_to_touch / adds_migration / touches_shared)
is authored at delegate time, consumed once by SequencingService to wire
dependency edges, then never shown to a reviewer again. This surfaces it:

- Pure builder (services/gateway/choreographer/collision.py): for a task
  under review, the surfaced siblings (same parent) that would collide —
  file-overlap globs or a shared migration chain (both adds_migration) —
  with the overlapping globs and a declared-vs-actual drift check. No
  DB/IO; callers fetch siblings (one indexed get_subtasks query, mig 069)
  + actual files (git). Caps: 10 siblings, 5 globs.

- Evidence envelopes: collision_context block injected into QA
  claim_review, PR-gate claim_gate_review (both carry real touched files
  so drift is populated), and the PM i_will_plan briefing (no actual
  files at plan time, drift omitted). Best-effort — a failure omits the
  block, never breaks the verb/briefing. Empty block omitted (zero token
  cost via _EVIDENCE_OMIT_WHEN_EMPTY).

- Panel: GET /api/tasks/{id}/collision-map (declared surface + sibling
  overlap; no drift — the panel route resolves no workspace) + a Collision
  tab on the task detail (8th tab). Mock-mode returns an empty map.

- docs/map added to the RAG auto-index dirs so the collision-map concept
  is fleet-retrievable; skipped gracefully if the dir is absent.

19 new tests (15 unit on the pure builder + 4 integration on the route).
Gate green: ruff/mypy/xenon (module rank A)/pytest 13000/coverage 94.81%,
panel typecheck/lint/516 tests.

* [w6-telegram] Add Telegram notifications bridge (V1)

CEO-facing Telegram DM bridge, flag-gated off by default
(ROBOCO_TELEGRAM_ENABLED). Mirrors the X-credentials / X-client pattern:

- TelegramCredentialsTable (migration 073) — singleton Fernet-encrypted
  bot_token + chat_id, all-or-nothing set/clear; API never returns plaintext.
- TelegramClient ABC / NullTelegramClient (no-op, configured->False, never
  raises) / LiveTelegramClient (httpx POST sendMessage) / build_telegram_client
  factory (Null when creds unset).
- /telegram/credentials CEO-only routes (write-only, guard-decorated).
- Best-effort _notify_telegram fan-out from the two CEO-notify producers
  (notify_ceo_of_escalation, notify_ceo_of_completion) — guarded by the flag,
  never raises into the producer, carries a panel deep-link when
  panel_base_url is set.
- panel credentials card (2 fields) nested in the Telegram feature-flag row.
- panel_base_url + telegram_timeout_seconds config fields.

V1 scope only: credentials + flag + panel card + client + one-line fan-out.
Out of scope (V2): inbound commands, a TelegramEngine background loop, a
dedup ledger, a bus subscription.

* [w6-telegram] fix: slave mypy/xenon regression (product tests + helper extract)

Pre-existing on slave from prior session's merges — no PR's CI caught them
(squash merges don't re-CI the result; each branch was based on older slave).

- test_product: _product helper returned MagicMock -> list invariant error;
  cast to ProductTable, move import under TYPE_CHECKING.
- test_usage: svc.session.execute (AsyncSession) has no call_args_list;
  cast to MagicMock at the two call sites.
- product.progress_for_products: xenon rank C -> extract module-level
  _project_to_products_map helper (repo pattern: helper-extract).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-15 05:45:57 +02:00
d80dfb8bbe feat(env-branches): per-project ordered environment ladder (replaces default_branch) (#534)
* [env-bran] EnvSyncEngine: orchestrator-side prod→dev cascade (default-off)

- EnvSyncEngine mirrors CiWatchEngine: cascade ladder_pairs top-down via
  GitHub merges API; clean→auto-push lower rung, conflict→one sync PR +
  tracked MAIN_PM task + stop. Never pushes prod (lower rung is never prod
  by construction).
- GitService.sync_env_branch (merges API) + open_sync_pr (idempotent) +
  _env_merge_status/_post_sync_pr helpers (constants for 201/204/409).
- TaskService.ENV_SYNC_SOURCE + list_open_env_sync_tasks (per-repo dedup).
- config env_sync_enabled/_interval_seconds(1800)/_max_open_tasks(3)/_max_per_cycle(1).
- Orchestrator 4-touch registration + _load_env_sync_set (ladder+token opt-in).
- Feature-flags card + settings FEATURE_FLAGS entry for ROBOCO_ENV_SYNC_ENABLED.

* [env-bran] Panel: environment ladder editor + types + validation

- EnvironmentRung type + environments on Project/ProjectCreate/ProjectUpdate.
- EnvironmentLadderEditor (plain useState, add/remove/up-down reorder, head/
  prod labels) reused by create + edit project dialogs.
- validateLadder (non-empty name+branch, no duplicate branches) shared,
  toast.error on submit; empty editor => null => inherits default_branch shim.
- default_branch input kept with override-hint; API client passthrough.
- 6 unit tests for validateLadder.

* [env-bran] Tests + gate green: env ladder, EnvSyncEngine, promotion chain

- tests/unit/models/test_env_branches.py: shim, head/prod, ladder_pairs,
  promotion_chain, normalize (20 tests)
- tests/integration/services/test_env_sync_engine.py: cascade clean/conflict/
  missing_ref/tokenless/degenerate/caps/dedup/disabled (9 tests, DB)
- tests/integration/test_migration_env_branches.py: 073 defaults null + round-trip
- tests/unit/services/test_release_executor*.py: add env_chain=[] to
  _ReleaseContext constructions (promotion_chain field is now required)
- tests/unit/runtime/test_orchestrator_shutdown_drain.py: register _env_sync_task
  in the stop()-drain fixture (new named background loop)
- roboco/services/git.py: revert _project_head_branch rename back to
  _project_default_branch (modify-in-place per plan); the rename in the
  consumers commit broke ~15 unit-test mocks that bind the original name
- roboco/services/env_sync_engine.py + models/env_branches.py: ruff format
- roboco/api/schemas/project.py: trailing-newline format

Backend gate green (13013 passed / 439 skipped), mypy clean, ruff clean.
Panel gate green (typecheck/lint/522 tests).

* [env-bran] fix: add env_chain to _ReleaseContext in e2e smoke (CI red)

The release-executor promotion_chain change made _ReleaseContext.env_chain
required. I fixed the three unit/release test files but missed the
construction in tests/e2e_smoke/test_background_engines.py:98 — my local
gate ran 'mypy roboco/' (excludes tests/) and I skipped 'make e2e-smoke',
so CI's mypy-on-tests + the e2e runtime job caught it instead of me.

Verified locally with the CI-equivalent gates:
  uv run mypy roboco/ tests/   -> 1170 files, clean
  ROBOCO_E2E_SMOKE=1 uv run pytest tests/e2e_smoke -> 50 passed, 1 skipped

* [env-bran] fix: extract _ensure_prod_fetched to clear xenon rank C (CI red)

_production_assess grew past xenon --max-absolute B (rank C) when the
env-branches prod-tip fetch added an if/try/except branch. Extracted the
fetch-with-fallback into _ensure_prod_fetched (degan+fetch paths), moved
_run_git to the module-level import. Local make quality green (all gates
incl xenon/vulture/deptry/import-linter/foundation-check).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-15 04:43:37 +02:00
b7f2d84c77 W9-5: Tooltip sweep — HelpTip helper + per-view decode (#533)
* [W9-5a] Tooltip sweep foundation: HelpTip helper + shared cryptic badges

HelpTip: DRY wrapper over the verbose 3-element Radix Tooltip pattern so a
broad sweep stays a one-line wrap per site (falsy label short-circuits to the
bare child). Unit-tested (3 cases).

TaskStatusBadge + AgentStateBadge: the panel's most cryptic, most-frequent
elements (15 task lifecycle states, 11 agent states) had no explanation
anywhere. Add a per-state tooltip via HelpTip, with the canonical text in one
description map and exported as taskStatusDescription / agentStateDescription
so the per-view inline renderers (kanban, task header) reuse it instead of
re-declaring. This is part 1 of the W9-5 tooltip sweep; the per-view inline
surfaces follow in subsequent PRs.

* [W9-5b] Per-view tooltip sweep: decode cryptic badges, icon-only buttons, status dots

35 HelpTip additions across 22 panel components, reusing the W9-5a
helper plus taskStatusDescription/agentStateDescription. Tipped:
task-id/commit-hash/branch/PR badges, severity/origin/status badges,
priority (P0-P3), migration/shared flags, MegaTask umbrella badge,
Review Gate / For Resumption / Confidential note badges, icon-only
view/delete/edit/clear/show-hide buttons, semver bump + gate-state
badges, ahead-not-pushed badge. Skipped self-explanatory labeled
buttons and elements already carrying title=.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-15 04:34:17 +02:00