Skip to content

Release v2.3.0 — brand-first quota widget + Gate Bar 0.1#217

Merged
github-actions[bot] merged 12 commits intomainfrom
fix/quota-widget-provider-groups
Apr 19, 2026
Merged

Release v2.3.0 — brand-first quota widget + Gate Bar 0.1#217
github-actions[bot] merged 12 commits intomainfrom
fix/quota-widget-provider-groups

Conversation

@typelicious
Copy link
Copy Markdown
Collaborator

Summary

v2.3.0 release — eight commits consolidating the brand-first quota widget refresh and the Gate Bar 0.1 macOS menubar companion.

Dashboard

  • Brand-card overview at /dashboard/quotas (worst-alert first, pace marker, identity line, per-brand credential gating).
  • Per-brand detail view at /dashboard/quotas/<slug> + three read-only endpoints (clients, routes, analytics).
  • dashboard.quotas.default_view setting with one-click Pin-as-Home. Writes go through ruamel.yaml round-trip so the 220+ operator comments in a real config.yaml survive a pin toggle — yaml.safe_dump would flatten them.
  • GET/POST /api/dashboard/settings drives the pin UI.

Gate Bar 0.1 (apps/gate-bar/)

  • SwiftUI MenuBarExtra app, macOS 14+, pure HTTP client against /api/quotas.
  • 13 Swift-Testing tests green on both Xcode.app and Command Line Tools.
  • Source-only release. Developers can ./apps/gate-bar/scripts/install-local.sh for a locally-built, ad-hoc-signed .app in ~/Applications. Notarized Homebrew cask ships with Gate Bar 0.2.

Dep change: ruamel.yaml>=0.18.6 added to requirements.txt + pyproject.toml.

Test plan

  • python3.12 -m pytest tests/test_dashboard_settings.py tests/test_brand_detail_endpoints.py (53 passed)
  • Gate Bar: ./apps/gate-bar/scripts/swift-test.sh (13 passed, 3 suites)
  • Gate Bar: ./apps/gate-bar/scripts/install-local.sh --no-open → bundle verified, ad-hoc signed, launches without Gatekeeper prompt
  • Real-life menubar test against live gateway (operator follow-up)
  • CI green on push

After merge

  1. git checkout main && git pull --ff-only
  2. git tag -a v2.3.0 -m "fusionAIze Gate v2.3.0"
  3. git push origin v2.3.0 → triggers release-artifacts.yml
  4. Publish GitHub Release from the tag → triggers notify-tap.yml

🤖 Generated with Claude Code

André Lange and others added 9 commits April 18, 2026 20:03
Reworks the /api/quotas surface so the dashboard widget matches how
CodexBar presents provider state:

- QuotaStatus gains a provider_group field; the widget groups packages
  under one provider card and subdivides by package inside it
- daily / rolling_window statuses accept extra_provider_ids so a shared
  quota (e.g. Google AI Studio free tier covers both gemini-flash and
  gemini-flash-lite) is counted across all router IDs that draw from it
- /api/quotas applies a credential gate: packages tagged with
  _requires_credential are hidden when the env var is missing /
  placeholder, or when the OAuth subject is not in the local token
  store. Skipped entries are reported back under skipped_packages so
  operators can see what's dormant and why.
- dashboard widget renders one card per provider_group with all its
  packages stacked (CodexBar-style), plus a small "hidden" callout
  listing credential-gated packages that aren't active yet
Phase A of the Gate Bar & Quota Widget redesign (docs/GATE-BAR-DESIGN.md):
operators now see Claude / Codex / Gemini / Kilo Code — the products they
actually use — instead of company keys like anthropic/openai/kilocode. The
router keeps reading provider_group/_id so scoring and lane logic are
untouched.

QuotaStatus gains four fields:

  - brand / brand_slug — operator-facing product name + URL-safe slug used
    by /dashboard/quotas/<brand_slug> and Cockpit deep links
  - pace_delta / elapsed_ratio — linear-pace indicator for rolling_window
    and daily packages; credits packages leave it None and lean on
    projected_days_left
  - identity — credential shape (API key vs OAuth) so the widget can render
    "Pro · OAuth" / "API · env ANTHROPIC_API_KEY" lines per brand

A fallback brand table covers pre-v1.3 catalogs, so nothing breaks if the
shared catalog lags. /api/quotas now also emits catalog_suggestions: brands
in the catalog that aren't active locally, ready to feed the widget's
upcoming "Available to add" mini-block (each carrying the authored
catalog_tagline in tier · price · quota shape grammar).

Coverage: 11 new tests in test_quota_tracker_brand.py pin the contract for
brand fallback, slug kebab-casing, identity derivation, pace math on
rolling/daily windows, and the to_dict() shape the API relies on. Full
suite: 395 passed.

Deferred to Phase B: the widget refresh that consumes these fields.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase B.1-B.3 of the v2.3 Gate Bar companion rollout. The quotas widget
now groups by ``brand_slug`` (Claude, Codex, DeepSeek, ...) instead of
``provider_group``, surfaces the identity line (API key vs OAuth) per
brand card, and renders a pace tick on rolling-window + daily bars so
operators can see at a glance whether they're burning faster than the
window elapses.

- Responsive 1/2/3-column grid sorts brands by worst alert first.
- Each brand card links to ``/dashboard/quotas/<slug>`` (Details, Phase
  B.4) and out to the Operator Cockpit via ``FAIGATE_COCKPIT_URL``
  (default ``https://cockpit.fusionaize.ai``, strip trailing slash).
- New ``_cockpit_base_url()`` helper + ``__COCKPIT_URL__`` placeholder
  replaced at render time so deep links compose cleanly.
- Catalog mini-block renders ``catalog_suggestions`` (max 6, +N more)
  with an "Add in Cockpit ↗" CTA — discovery without write paths in the
  widget itself.
- Skipped block labels by brand name, keeping the missing-credential
  story readable.

See docs/GATE-BAR-DESIGN.md §3 for the Design-Thinking rationale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase B.4 of the v2.3 Gate Bar companion rollout. Drilling into a brand
card now lands on ``/dashboard/quotas/<brand_slug>`` — a quick-view
subset of the Operator Cockpit (quota card, clients, routes, sparkline)
that's read-only; every write path links out to the Cockpit.

New HTTP surfaces (all scoped to the active providers behind a brand):

- ``GET /api/quotas/<slug>/clients``   — client_profile + client_tag
  breakdown + totals, filtered to the brand's providers.
- ``GET /api/quotas/<slug>/routes``    — lane_family / routing /
  selection_path breakdown for the brand.
- ``GET /api/quotas/<slug>/analytics`` — hourly (last 24h, max 1 week)
  + daily (last 14d, max 90d) series + totals + per-provider summary.
  ``hours`` / ``days`` are clamped so a URL typo can't hammer SQLite.
- ``GET /dashboard/quotas/<slug>``     — self-contained HTML shell that
  polls all four APIs on load + every 60s; substitutes ``BRAND_SLUG``
  + ``COCKPIT_URL`` at render time.

All four endpoints 404 on unknown or inactive brands so the widget can
distinguish "typo in URL" from "brand exists but no traffic yet".

Metrics layer gets two small additions (backward compatible):

- ``_build_where_clause`` now accepts ``providers=[...]`` (rendered as
  ``provider IN (...)`` with dedup) and ``since=<ts>`` — lets the new
  endpoints aggregate across multiple runtime providers in one query.
- ``get_hourly_series`` / ``get_daily_totals`` accept ``**filters`` so
  the detail view's sparkline is brand-scoped, not global.

Test coverage: 14 new tests in ``test_brand_detail_endpoints.py``
pinning the 404 contract, the providers-filter round-trip through
metrics, query clamping, and the HTML placeholder substitution. Full
suite: 409 passed.

See docs/GATE-BAR-DESIGN.md §3.4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase C of the v2.3.0 Gate Bar rollout. Ships a macOS 14+ Universal
menubar app at ``apps/gate-bar/`` that reads the local faigate
gateway's ``/api/quotas`` and surfaces every active brand at a glance —
colour-coded by severity, sorted worst-alert first.

Architecture keeps the boundary sharp (see docs/GATE-BAR-DESIGN.md §5):

- Pure HTTP client on 127.0.0.1 — no shared state, no socket, no
  filesystem coupling with the Python daemon.
- Brand roster discovered at runtime; the app ships with no hard-coded
  provider enum.
- Read-only: every mutating action (add provider, edit lanes, etc.)
  deep-links to the Operator Cockpit.

SwiftUI surface stays in the Sonoma subset per the design doc:
``ObservableObject`` + Combine, plain ``Color``, no ``@Observable``, no
``MeshGradient``. Package.swift pins ``.macOS(.v14)``.

Module layout:

- ``Models.swift``      — Codable mirror of ``/api/quotas`` (forward-
  compatible decode so the Python side can add fields without breaking
  Gate Bar), plus ``BrandGroup`` + ``AlertLevel`` aggregates.
- ``QuotaClient.swift`` — URLSession actor, single network surface.
- ``QuotaStore.swift``  — ``ObservableObject`` that groups packages by
  ``brand_slug``, sorts worst-alert first, and exposes a tightest-
  window menubar summary.
- ``Preferences.swift`` — UserDefaults-backed ``@Published`` wrapper
  (gateway URL, cockpit URL, refresh cadence).
- ``Theme.swift``       — colour palette mirroring the web widget's
  CSS variables so the menubar reads as the same product.
- ``PopoverView.swift`` / ``BrandCardView.swift`` — popover shell +
  per-brand card with pace tick (same vocabulary as §3 of the design).
- ``PreferencesView.swift`` — settings window, 4 controls, no wizards.
- ``GateBarApp.swift``  — ``@main`` + ``MenuBarExtra`` / ``Settings``
  scenes; menubar label is "fAI · NN%" with a coloured dot.

Tests (13, Swift Testing framework):

- JSON decode round-trip + forward-compatibility.
- AlertLevel classification (server-label precedence, ratio fallback,
  unknown-string degradation).
- Store grouping, worst-alert sort, tie-break rules, menubar summary.

CLT-only machines need explicit rpaths for Swift Testing — wrapped in
``scripts/swift-test.sh`` so ``./scripts/swift-test.sh`` works whether
the dev has Xcode.app installed or not.

Out of scope for 0.1 (tracked in apps/gate-bar/README.md): Sparkle
auto-update, notifications, launch-at-login, .app bundling +
notarization, Homebrew cask.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Persisted dashboard.quotas.default_view setting honoured by
/dashboard/quotas via server-side 302. Three values: overview (grid,
default), brand:<slug> (detail page), cockpit (offsite). The escape
hatch ?view=overview always renders the grid so a pinned brand card
can link home without fighting the redirect.

Persistence uses ruamel.yaml round-trip so the 220+ operator comments
in config.yaml survive a pin toggle — yaml.safe_dump would flatten
them. Writes are atomic (tempfile.mkstemp + os.replace in the same
dir) so a crash mid-write can't leave a half-rewritten config.

HTTP surface:
- GET  /api/dashboard/settings — {default_view, pinned_brand_slug}
- POST /api/dashboard/settings — 400 on bad input, 200 with canonical
  settings on success
- GET  /dashboard/quotas — honours default_view, falls back to the
  overview if settings read fails (never 500s)

UI:
- Overview: 'Home view' chip in header + 'Reset to Overview' when
  anything other than overview is pinned.
- Every brand card: 'Pin as Home' / '📌 Home' button next to
  Details/Cockpit. Pinned card gets a subtle accent outline.
- Detail page header: same pin button.
- Gate Bar popover footer: 'Dashboard ↗' link to
  <gateway>/dashboard/quotas. Server-side redirect handles default_view
  so the menubar app doesn't branch.

Tests: 39 new cases in tests/test_dashboard_settings.py cover
round-trip comment preservation, validation rules, atomic rename,
endpoint contracts, redirect behaviour, and bad-config fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Until Gate Bar 0.2 ships a notarized Homebrew cask, developers who
want to try the menubar app on their own machine can run:

    cd apps/gate-bar
    ./scripts/install-local.sh

The script does a release build, wraps the binary in a minimal .app
bundle (LSUIElement so no Dock icon, macOS 14+ min version),
ad-hoc code-signs it, and installs to ~/Applications. Because the
binary is built and signed on the same machine, Gatekeeper trusts it
without a Developer ID round-trip — no quarantine xattr means no
first-launch prompt.

Flags: --no-open skips the auto-launch, --uninstall removes the
installed bundle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Consolidates six commits on this branch into a single release:

- Brand-card overview at /dashboard/quotas (worst-alert first, pace
  marker, identity line, per-brand credential gating).
- Per-brand detail view at /dashboard/quotas/<slug> + three read-only
  endpoints backing it (clients, routes, analytics).
- Default landing view (dashboard.quotas.default_view in config.yaml)
  with Pin-as-Home on every card; writes go through ruamel.yaml
  round-trip so operator comments survive a pin toggle.
- Gate Bar 0.1 SwiftUI menubar companion at apps/gate-bar/ — reads
  /api/quotas, renders cards, links to Dashboard and Cockpit.
  Source-only for now; notarized cask ships with Gate Bar 0.2.

New runtime dep: ruamel.yaml>=0.18.6 (required for the
comment-preserving dashboard.quotas.default_view writes).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts in faigate/main.py and faigate/quota_tracker.py by
keeping this branch's superset (brand/pace/identity on QuotaStatus +
brand-first dashboard). Main had the earlier provider-group-only
version of the quota commit; our branch has that plus the follow-up
brand naming work.
@github-actions github-actions bot enabled auto-merge (squash) April 19, 2026 17:02
Comment thread faigate/main.py Fixed
Comment thread faigate/main.py Fixed
Comment thread faigate/main.py Fixed
Comment thread faigate/main.py Fixed
André Lange added 3 commits April 19, 2026 19:08
Three classes of fixes:

1. Reflective XSS (high severity) on /dashboard/quotas/{brand_slug}:
   The slug from the URL path was spliced into the HTML template via
   str.replace without validation, so a crafted slug containing HTML
   could escape the attribute context. Now whitelisted to
   [a-z0-9][a-z0-9-]{0,63} — matches every real catalog brand and
   rejects anything else with 404.

2. Stack-trace exposure (medium, x3) on POST /api/dashboard/settings:
   Error responses included str(exc) for both ValueError and the
   catch-all, which can leak internal paths or user-supplied fragments.
   Replaced with static messages; the real exception is logged via
   logger.exception.

3. Ruff F401: removed unused imports shutil (test_dashboard_settings)
   and timedelta (test_quota_tracker_brand) flagged by the lint CI job.
Three follow-up fixes for PR #217 CI:

1. pyproject.toml: ruff target-version py312 → py310. The project's
   requires-python is >=3.10, but with py312 ruff's UP017 rule was
   rewriting datetime.timezone.utc to the 3.11-only datetime.UTC,
   breaking the 3.10 CI matrix job with ImportError.

2. test_quota_tracker_brand.py: switch UTC import to timezone.utc so
   the tests actually run on 3.10.

3. faigate/main.py: pass the already-whitelisted brand slug through
   html.escape before splicing into the HTML template. CodeQL's taint
   tracker doesn't recognise arbitrary regex sanitisers, so this
   satisfies py/reflective-xss without relaxing the regex guard above.

4. faigate/dashboard_settings.py: ruff format pass (cosmetic).
…ules

faigate's requires-python is >=3.10 but three modules imported the 3.11+
datetime.UTC alias directly. Main's CI happened to stay green because no
test module imported these three files at collection time — my new
test_quota_tracker_brand.py exposed the latent bug on the 3.10 matrix
job.

Files fixed:
  faigate/quota_tracker.py  — 4x datetime.now(UTC), 1x tzinfo=UTC, 1x tz=UTC
  faigate/quota_headers.py  — 2x datetime.now(UTC), 2x .replace/.astimezone
  faigate/quota_poller.py   — 2x datetime.now(UTC)

Already-correct files left untouched: updates.py and tests/test_updates.py
both use the canonical try/except ImportError shim.
@github-actions github-actions bot merged commit 8393b5d into main Apr 19, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants