feat(dashboard): improve Provider Quota page horizontal density (#3520) (#6815)

QuotaCardGrid stacked every provider group vertically in a single
flex flex-col container, and each group's own card grid didn't go
multi-column until the md breakpoint. Provider groups now flow into a
2-column CSS multi-column layout on very wide (2xl) screens instead of
an unconditional vertical stack, and each group's card grid starts at
2 columns immediately, filling horizontal whitespace sooner on
narrower-but-not-mobile viewports.

Regression guard: tests/unit/quota-card-grid-horizontal-layout.test.ts
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-11 04:31:52 -03:00
committed by GitHub
parent 7fae224315
commit 6d3d122b84
3 changed files with 124 additions and 3 deletions

View File

@@ -25,6 +25,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
- **feat(providers):** custom models now support a manual **Context Window Override** so an operator can correct a provider's misreported context length (e.g. reports 1M when the real limit is 128K) instead of the model silently getting dropped from combo routing once the wrong value lands in the catalog ([#4125](https://github.com/diegosouzapw/OmniRoute/issues/4125) — thanks @rucciva). Reuses the existing Feature-5004 `model_context_overrides` table (`source: "manual"`) — already the priority-0 source `getModelContextLimit()` (the function combo's context-window filter calls) reads ahead of the models.dev/registry/static catalog — so no new resolver logic was needed, only the missing write path: `PUT /api/provider-models` now accepts an optional `contextWindowOverride` (number to set, `null` to clear), `GET` surfaces the current value back on each custom-model row, and the provider detail page's custom-model edit form gained a Context Window Override field + badge. Regression guard: `tests/unit/provider-models-context-window-override-4125.test.ts`.
- **fix(providers):** register OpenRouter as a rerank provider so `openrouter/cohere/rerank-*` models resolve instead of erroring `Invalid rerank model` (#6574 — thanks @rafpigna)
- **fix(api):** `HEAD` requests no longer hang until client timeout on any route — valid, unknown, authed, or unauthed ([#6400](https://github.com/diegosouzapw/OmniRoute/issues/6400)), broader follow-up to the route-specific #6517 (`/v1/models`). Root cause: Next.js 16's App Router _route-handler_ pipeline (`next/dist/server/send-response.js`) correctly skips piping a `Response` body for `HEAD`, but its _page_-rendering pipeline (`next/dist/server/pipe-readable.js``pipeToNodeResponse`, used for every app-router page/layout render — including the `not-found` boundary any unmatched path falls through to) has no such check and always streams the full rendered body regardless of method; combined with Node's default keep-alive framing this left some clients unsure whether the (implicitly bodyless) `HEAD` response had actually finished. A new `scripts/dev/head-response-guard.cjs`, wired into both the dev/start custom server (`scripts/dev/run-next.mjs`) and the packaged standalone server (`scripts/dev/standalone-server-ws.mjs`) at the same tier as the existing `http-method-guard.cjs`/`peer-stamp.mjs` wrappers, discards any body bytes written for a `HEAD` request and forces `Connection: close` once `.end()` is called — independent of route existence or auth state, satisfying RFC 9110 §9.3.2. Regression guard: `tests/unit/head-request-closes-6400.test.ts`.
- **feat(dashboard):** Provider Quota page (`Dashboard → Quota`) fills horizontal whitespace before stacking vertically ([#3520](https://github.com/diegosouzapw/OmniRoute/issues/3520)) — `QuotaCardGrid` previously stacked every provider group in a single vertical `flex flex-col`, and each group's own card grid didn't go multi-column until `md` (`grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4`). Provider groups now flow into a 2-column CSS multi-column layout on very wide (`2xl`) screens instead of an unconditional vertical stack, and each group's card grid starts at 2 columns immediately (`grid-cols-2 md:grid-cols-3 xl:grid-cols-4`), reaching higher density sooner on narrower-but-not-mobile viewports. Regression guard: `tests/unit/quota-card-grid-horizontal-layout.test.ts` (thanks @gdevenyi).
### 🐛 Bug Fixes

View File

@@ -47,16 +47,16 @@ export default function QuotaCardGrid({
}
return (
<div className="flex flex-col gap-6">
<div className="columns-1 2xl:columns-2 gap-6 [column-fill:_balance]">
{[...groups.entries()].map(([provider, conns]) => (
<div key={provider} className="flex flex-col gap-3">
<div key={provider} className="flex flex-col gap-3 break-inside-avoid mb-6">
<h3 className="text-sm font-semibold text-text-main flex items-center gap-2">
{providerLabels[provider] || provider}
<span className="text-xs font-normal text-text-muted">
({conns.length} account{conns.length !== 1 ? "s" : ""})
</span>
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3">
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3">
{conns.map((conn) => (
<QuotaCard
key={conn.id}

View File

@@ -0,0 +1,120 @@
// #3520 — Provider Quota page should use horizontal whitespace better.
//
// QuotaCardGrid previously stacked provider groups vertically via a single
// `flex flex-col` container and kept cards to a conservative 1/2/3/4-column
// breakpoint ladder starting at `grid-cols-1`. This regression guard asserts
// the shipped JSX structure and grouping logic directly:
// 1. Grouping still produces one header per distinct provider with the
// correct account count ("N account(s)").
// 2. The per-group card grid starts multi-column (`grid-cols-2`), not
// single-column, so cards fill horizontal space sooner.
// 3. Provider groups themselves flow into multiple columns on wide screens
// (`columns-*`) instead of an unconditional vertical `flex flex-col`
// stack.
//
// Note: QuotaCardGrid's sibling QuotaCard pulls in next/image + provider-icon
// resolution that only works inside the real Next.js runtime, so this file
// exercises the two testable seams directly instead of full SSR-rendering the
// tree: (a) the pure grouping function extracted below, mirroring the
// component's own grouping logic, and (b) the literal className contract of
// the component's JSX (static string literals, not derived at runtime),
// parsed from source via the TypeScript compiler API so the assertions track
// the real shipped markup rather than a hand-copied string.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import ts from "typescript";
const COMPONENT_PATH = path.resolve(
import.meta.dirname,
"../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx"
);
function groupByProvider<T extends { provider: string }>(connections: T[]): Map<string, T[]> {
const groups = new Map<string, T[]>();
for (const conn of connections) {
const list = groups.get(conn.provider) ?? [];
list.push(conn);
groups.set(conn.provider, list);
}
return groups;
}
test("QuotaCardGrid (#3520) — groups connections by provider with correct counts", () => {
const groups = groupByProvider([
{ id: "conn-a1", provider: "openai" },
{ id: "conn-a2", provider: "openai" },
{ id: "conn-b1", provider: "anthropic" },
]);
assert.deepEqual([...groups.keys()], ["openai", "anthropic"]);
assert.equal(groups.get("openai")!.length, 2);
assert.equal(groups.get("anthropic")!.length, 1);
});
/**
* Extract the string literal passed to `className={...}` (or `className="..."`)
* for every JSX `<div>` opening element in the component's `return (...)` JSX,
* in source order, via the TypeScript compiler API (not a hand-rolled regex —
* tracks the real AST so it can't be fooled by comments/whitespace).
*/
function extractDivClassNames(sourcePath: string): string[] {
const sourceText = fs.readFileSync(sourcePath, "utf8");
const sourceFile = ts.createSourceFile(
sourcePath,
sourceText,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX
);
const classNames: string[] = [];
function visit(node: ts.Node) {
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
const tagName = node.tagName.getText(sourceFile);
if (tagName === "div") {
for (const attr of node.attributes.properties) {
if (ts.isJsxAttribute(attr) && attr.name.getText(sourceFile) === "className") {
const init = attr.initializer;
if (init && ts.isStringLiteral(init)) {
classNames.push(init.text);
} else if (
init &&
ts.isJsxExpression(init) &&
init.expression &&
ts.isStringLiteral(init.expression)
) {
classNames.push(init.expression.text);
}
}
}
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return classNames;
}
test("QuotaCardGrid (#3520) — outer container flows groups into multiple columns, not a single vertical stack", () => {
const [outerClassName] = extractDivClassNames(COMPONENT_PATH);
assert.ok(outerClassName, "expected the component to render an outer <div className=...>");
assert.match(outerClassName, /\bcolumns-/);
assert.notEqual(outerClassName, "flex flex-col gap-6");
});
test("QuotaCardGrid (#3520) — per-group card grid starts multi-column (grid-cols-2), not single-column", () => {
const classNames = extractDivClassNames(COMPONENT_PATH);
const cardGridClassName = classNames.find(
(c) => /\bgrid\b/.test(c) && /grid-cols-/.test(c)
);
assert.ok(cardGridClassName, "expected to find the per-group card grid's className");
assert.match(cardGridClassName!, /\bgrid-cols-2\b/);
assert.doesNotMatch(cardGridClassName!, /\bgrid-cols-1\b/);
});
test("QuotaCardGrid (#3520) — early-returns null when there are no connections", () => {
const sourceText = fs.readFileSync(COMPONENT_PATH, "utf8");
assert.match(sourceText, /if\s*\(\s*connections\.length\s*===\s*0\s*\)\s*return\s*null;/);
});