diff --git a/Combo-Context-Requirements.md b/Combo-Context-Requirements.md new file mode 100644 index 0000000..60436de --- /dev/null +++ b/Combo-Context-Requirements.md @@ -0,0 +1,265 @@ +> 🌍 [View in other languages](Languages) + + +# Combo Context Requirements Feature + +## Overview + +The Context Requirements feature allows combo configurations to filter and sort targets based on their context window size. This is useful for use cases requiring large context windows like: + +- Long document processing (100k+ tokens) +- Large codebase analysis +- Extensive conversation histories +- Multi-file code reviews + +## Configuration + +### Schema + +Add `contextRequirements` to your combo's runtime config: + +```json +{ + "contextRequirements": { + "minContextWindow": 128000, + "preferLargeContext": true, + "contextFilterMode": "strict" + } +} +``` + +### Fields + +#### `minContextWindow` (optional) + +- **Type**: `number` (0 to 10,000,000) +- **Default**: `undefined` (no filtering) +- **Description**: Filters out models with context windows below this threshold + +**Examples**: + +- `32000` - Filter out models with <32K context +- `128000` - Require 128K+ context (GPT-4 Turbo, Claude 3) +- `200000` - Require 200K+ context (Claude 3 Opus) +- `1000000` - Require 1M+ context (Gemini 1.5 Pro) + +#### `preferLargeContext` (optional) + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: When `true`, sorts remaining targets by context size (descending). Large context models are tried first. + +#### `contextFilterMode` (optional) + +- **Type**: `"strict"` | `"lenient"` +- **Default**: `"lenient"` +- **Description**: How to handle models with unknown context window limits + - `"strict"`: Excludes models with unknown context limits + - `"lenient"`: Includes models with unknown context limits + +## Behavior + +### Filtering Pipeline + +Context requirements are applied after `filterTargetsByRequestCompatibility()`: + +1. **Request compatibility filtering** - Removes models incompatible with request (tools, vision, structured output) +2. **Context requirements filtering** - Applies `minContextWindow` and `contextFilterMode` +3. **Context-based sorting** - If `preferLargeContext` is true, sorts by context size descending + +### Filter Mode Logic + +When `minContextWindow` is set: + +**Lenient mode** (default): + +- βœ… Includes models with context >= minContextWindow +- βœ… Includes models with unknown context limits +- ❌ Excludes models with context < minContextWindow + +**Strict mode**: + +- βœ… Includes models with context >= minContextWindow +- ❌ Excludes models with unknown context limits +- ❌ Excludes models with context < minContextWindow + +### Sorting Logic + +When `preferLargeContext` is true: + +- Models are sorted by context window size (descending) +- Unknown context models sort to the end +- Original strategy order is used as a tiebreaker + +## Use Cases + +### Example 1: Long Document Processing + +```json +{ + "name": "Document Analysis", + "strategy": "fusion", + "config": { + "contextRequirements": { + "minContextWindow": 128000, + "preferLargeContext": true, + "contextFilterMode": "strict" + } + } +} +``` + +This configuration: + +- Requires 128K+ context window +- Prefers larger context models (Gemini 1.5 Pro > Claude 3 Opus > GPT-4 Turbo) +- Excludes models with unknown context limits + +### Example 2: Large Codebase Analysis + +```json +{ + "name": "Code Review", + "strategy": "auto", + "config": { + "contextRequirements": { + "minContextWindow": 200000, + "preferLargeContext": true, + "contextFilterMode": "lenient" + } + } +} +``` + +This configuration: + +- Requires 200K+ context window +- Prefers larger context models +- Includes models with unknown limits (lenient) + +### Example 3: Prefer Large Context Without Strict Requirements + +```json +{ + "name": "Flexible Chat", + "strategy": "weighted", + "config": { + "contextRequirements": { + "preferLargeContext": true + } + } +} +``` + +This configuration: + +- No minimum requirement (all models eligible) +- Sorts by context size (largest first) +- Useful when large context is preferred but not required + +## API Response + +When context requirements filter targets, the combo logger outputs: + +``` +[COMBO] Context requirements: filtered 10 β†’ 3 targets (minContextWindow: 128000, mode: strict) +[COMBO] Context requirements: kept models gemini-1.5-pro, claude-3-opus-20240229, gpt-4-turbo +[COMBO] Context requirements: sorted by context size (descending): gemini-1.5-pro(1000000), claude-3-opus-20240229(200000), gpt-4-turbo(128000) +``` + +## Implementation Details + +### Backend Module + +`open-sse/services/combo/contextRequirements.ts`: + +- `applyContextRequirements()` - Main filtering function +- `getTargetContextWindow()` - Context lookup helper +- Uses `getModelContextLimit()` from `modelCapabilities.ts` + +### Integration Point + +`open-sse/services/combo.ts` line 1187: + +```typescript +orderedTargets = filterTargetsByRequestCompatibility(orderedTargets, body, log); +orderedTargets = applyContextRequirements(orderedTargets, config.contextRequirements, log); +``` + +### Schema Definition + +`src/shared/validation/schemas/combo.ts`: + +```typescript +contextRequirements: z + .object({ + minContextWindow: z.coerce.number().int().min(0).max(10_000_000).optional(), + preferLargeContext: z.boolean().optional(), + contextFilterMode: z.enum(["strict", "lenient"]).optional(), + }) + .strict() + .optional(), +``` + +## Testing + +### Run Tests + +```bash +# Unit tests (schema + logic) +npm test tests/unit/combo-context-requirements.test.ts + +# Integration tests (end-to-end) +npm test tests/unit/combo/context-requirements-integration.test.ts +``` + +### Test Coverage + +- Schema validation: 6 tests +- Filtering logic: 6 tests +- Integration: 5 tests +- **Total**: 17/17 passing βœ… + +## Troubleshooting + +### All targets filtered out + +**Problem**: All targets removed, combo returns "no compatible models" + +**Solutions**: + +1. Lower `minContextWindow` threshold +2. Switch to `"lenient"` mode to include unknown context models +3. Remove `minContextWindow` and use only `preferLargeContext` + +### Unknown context models excluded + +**Problem**: Custom/new models excluded even though they have large context + +**Solutions**: + +1. Switch to `"lenient"` mode (default) +2. Add model context limit to `modelCapabilities.ts` +3. Remove context filtering and rely on strategy order + +### Sorting not applied + +**Problem**: `preferLargeContext` doesn't change order + +**Check**: + +1. Verify `preferLargeContext: true` in config +2. Check if all targets have unknown context (all sort equal) +3. Verify multiple targets remain after filtering + +## Related + +- [Auto-Combo Routing Strategies](./routing/AUTO-COMBO.md) +- [Resilience Guide](./architecture/RESILIENCE_GUIDE.md) + +## Version History + +- **v3.8.47**: Initial implementation + - Added `contextRequirements` config + - Created backend filtering module + - Full test coverage (no dedicated dashboard UI yet β€” configure via combo JSON) diff --git a/Design-System.md b/Design-System.md new file mode 100644 index 0000000..564483e --- /dev/null +++ b/Design-System.md @@ -0,0 +1,230 @@ +> 🌍 [View in other languages](Languages) + + +# OmniRoute β€” Design System & Visual Identity + +> **Status:** reference β€” the standardization described here is **implemented** (phases 1–6: grid wallpaper, primitives, status-color centralization, mono token, DataTable token migration, focus-ring β†’ accent, Checkbox/Textarea primitives, `cn()` β†’ tailwind-merge, grid on every standalone screen, fluid 4K content shell, opaque data-table surfaces). This document is the canonical description of the dashboard's design tokens, components, and conventions; the phase framing below is kept as the rationale for each decision. +> **Scope:** the OmniRoute dashboard (`src/`) and the marketing site (`_mono_repo/omnirouteSite/`) share **one visual identity** β€” same graph-paper grid background (32px), same color tokens, standardized components. +> +> Practical notes for maintainers: +> +> - Several remaining hardcoded hex values are **intentional** (always-dark console terminal, ReactFlow SVG strokes) and must **NOT** be swept into tokens. +> - A "bigger" grid on a running instance is a stale build, not code β€” the grid size is 32px, identical to the site. +> - Dark-theme `--table-*` values are byte-identical to the pre-migration hardcoded rgba; light theme was fixed (it was buggy always-dark via dead `var()` fallbacks). + +--- + +## 1. Purpose + +The marketing site (`viral.omniroute.online`, `why.omniroute.online`, `omniroute.online`) and the product dashboard should look like **one product**. The site already borrowed its palette from the dashboard β€” its `css/tokens.css` even says _"Palette mirrors the OmniRoute dashboard (src/app/globals.css)"_. So the two are already ~80% aligned at the color level. What's missing on the dashboard: + +1. The **graph-paper grid wallpaper** the site uses on every page. +2. A handful of **shared design tokens** the site has but the dashboard lacks (radius scale, brand gradient, `surface-2`, mono font). +3. **Component-level consistency** β€” a number of dashboard components bypass the theme tokens with hardcoded hex/rgba. + +This document is the analysis and the plan. + +--- + +## 2. Principles + +- **Single source of truth = `src/app/globals.css`.** The site mirrors the dashboard, never the other way around. New tokens land in `globals.css` first. +- **Tokens, never literals.** Components consume semantic tokens (`bg-surface`, `text-primary`, `border-border`), never raw `#hex`. +- **Subtle, not loud.** The grid is a faint wallpaper that sits behind content β€” it must never reduce text contrast or fight the UI. +- **Theme-aware.** Everything works in both `.dark` (the product's signature look) and light. +- **Surgical rollout.** Ship the grid + tokens first (low risk, high visibility), then component cleanups in waves. + +--- + +## 3. Current state β€” what's already aligned vs. what's not + +### 3.1 Colors β€” already unified βœ… + +Every brand color and surface already matches the site **by value** (only the names differ β€” dashboard prefixes with `--color-`). Verified in `src/app/globals.css:30-128`: + +| Concept | Site token (`tokens.css`) | Dashboard token (`globals.css`) | Match | +| -------------------------- | ------------------------------------------- | ------------------------------- | ------------ | +| primary | `--primary #e54d5e` | `--color-primary #e54d5e` | βœ… | +| primary-hover | `--primary-hover #c93d4e` | `--color-primary-hover #c93d4e` | βœ… | +| accent | `--accent #6366f1` | `--color-accent #6366f1` | βœ… | +| accent-2 | `--accent-2 #8b5cf6` | `--color-accent-hover #8b5cf6` | βœ… (renamed) | +| accent-3 | `--accent-3 #a855f7` | `--color-accent-light #a855f7` | βœ… (renamed) | +| success / warning / error | `#22c55e / #f59e0b / #ef4444` | identical | βœ… | +| traffic lights | `#ff5f56 / #ffbd2e / #27c93f` | identical | βœ… | +| dark bg / surface / border | `#0b0e14 / #161b22 / rgba(255,255,255,.08)` | identical | βœ… | +| light bg / surface / text | `#f9f9fb / #fff / #1a1a2e` | identical | βœ… | + +**Conclusion:** there is no color migration to do. The identity is already shared; we are _finishing_ it, not rebuilding it. + +### 3.2 Gaps β€” what the dashboard is missing + +| Gap | Site has | Dashboard | Action | +| ----------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------- | +| **Grid wallpaper** | `body::before` graph-paper, `--grid-line`, `--grid-size 32px`, `--section-alt` | **βœ… added (Phase 1)** | **Part A** | +| **Radius scale** | `--radius 14px`, `--radius-sm 9px` | `--radius 14px` added; `-sm` + component repoint pending | **Part B / Phase 2** | +| **Brand gradient** | `--grad-brand 135deg primaryβ†’accent-3` | **βœ… token added (Phase 1)**; consumed in Phase 2 | **Part B** | +| **Nested surface** | `--surface-2 #1c2230` | **βœ… added (Phase 1)** | **Part B** | +| **Mono font** | `--font-mono` (ui-monospace stack) | pending (Phase 4, with consumers) | **Part B** | +| **`text-muted` (dark)** | `#8b8b9e` | `#a1a1aa` (zinc-400) | reconcile β€” **Part B** | + +### 3.3 Theming mechanics (so we don't break anything) + +- **Tailwind v4, CSS-first** (no `tailwind.config.*`). Tokens are defined in `:root`/`.dark` and exposed to utilities via `@theme inline` (`globals.css:130-179`). +- **Dark via `.dark` class** on `` (`@custom-variant dark` at `globals.css:22`), toggled by a custom Zustand store (`src/store/themeStore.ts`), default theme = `system` (`src/shared/constants/appConfig.ts:11`). The site uses `html[data-theme="light"]` instead β€” **the mechanisms differ but never meet** (separate origins), so no conflict. We keep the dashboard's `.dark` mechanism. +- **Runtime primary override** exists (`themeStore.ts:85-97`, presets in `COLOR_THEMES`) β€” users can swap `--color-primary`. Any new token (gradient, etc.) that references `--color-primary` inherits those overrides for free. βœ… +- **Tailwind v4 reserved radius names:** `--radius-sm/md/lg/...` back the `rounded-*` utilities. Redefining them retroactively changes every existing `rounded-*` (e.g. `rounded-sm` is used in 12 files). So the small-radius value and component repoint are deliberately deferred to Phase 2, where consumers change together. + +--- + +## 4. Part A β€” The graph-paper grid background (headline ask) β€” IMPLEMENTED (Phase 1) + +### 4.1 What it is + +The exact recipe from the site (`_mono_repo/omnirouteSite/css/base.css`): a **fixed, full-viewport pseudo-element** painting two 1px line gradients, sitting at `z-index:-1` behind all content. + +```css +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background-image: + linear-gradient(to right, var(--grid-line) 1px, transparent 1px), + linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px); + background-size: var(--grid-size) var(--grid-size); +} +``` + +**Why this works even though `body` has an opaque `background-color`:** a `::before` with `z-index:-1` paints _above_ the element's own background but _below_ its in-flow content. So `--color-bg` is the base fill, the grid is layered on top of it, and the app renders above the grid. + +### 4.2 Precedent already in the codebase + +`src/app/landing/page.tsx:16-26` **already implements this same grid per-page** β€” but with **red** lines (`#E54D5E`, opacity `0.06`) at **50px**, plus animated orbs. So the pattern is proven in the product; this work promotes it to a **global, theme-aware** wallpaper. + +### 4.3 Tokens added (in `globals.css`) + +```css +:root { + /* light β€” grid opacity tuned up from the site's 0.045 so the wallpaper is + actually visible on the dense dashboard (cards/chrome cover most of the viewport) */ + --grid-line: rgba(0, 0, 0, 0.07); + --grid-size: 32px; + --section-alt: rgba(0, 0, 0, 0.022); +} +.dark { + /* dark β€” tuned up from 0.035 for the same reason */ + --grid-line: rgba(255, 255, 255, 0.06); + --section-alt: rgba(255, 255, 255, 0.018); +} +``` + +### 4.4 The single blocker β€” removed + +The grid is global by construction (it covers the panel, `auth`/`login`, error pages β€” every route β€” at once). Exactly **one** element hid it inside the panel: + +- `src/shared/components/layouts/DashboardLayout.tsx` β€” the outer wrapper painted an opaque `bg-bg`. Everything below it is already transparent (`
`, the scroll container, the `max-w-7xl` inner), so **removing `bg-bg`** lets the body grid show through the content area (the body's `--color-bg` remains the base fill). + + ```diff + -
+ +
+ ``` + +### 4.5 Chrome interaction (sidebar / header) + +- `Header` (`Header.tsx:207`, `bg-bg`) and `Sidebar` (`Sidebar.tsx:430`, `bg-sidebar`) stay **opaque** β†’ the grid shows in the **content area only**, with solid chrome framing it. Calm default, matches how the site separates chrome from canvas (decision D3 = solid). + +### 4.6 Login / auth / error pages + +These render directly under `` (no panel chrome), so the global grid should appear behind them automatically. **Phase 5 β€” DONE:** the standalone full-screen wrappers were in fact opaque (`min-h-screen … bg-bg`, where `bg-bg` is the same solid fill as ``), which hid the grid on every non-dashboard screen β€” not just login. All of them are now transparent so the shared wallpaper shows through: `login`, `forgot-password`, `callback`, `maintenance`, `offline`, `status`, `terms`, `privacy`, `onboarding`, and `ErrorPageScaffold` (covers `400`/`401`). This closes **D4** (extended from login-only to every standalone screen). Guarded by `tests/unit/design-grid-background.test.ts`. + +### 4.7 Landing page + +`landing/page.tsx` keeps its richer animated background (orbs + vignette) β€” its own marketing splash (decision D5 = leave as-is). + +--- + +## 5. Part B β€” Token unification + +Phase 1 adds the inert, collision-free identity tokens (`--surface-2`/`--color-surface-2`, `--grad-brand`, `--radius`). Phase 2 wires the radius scale into Tailwind and repoints components; Phase 4 adds `--font-mono` with its consumers. + +| Token | Why | Phase | +| -------------------------- | --------------------------------------------------------------- | ------------------------------ | +| `--radius` / `--radius-sm` | One radius scale (14/9) instead of 6/8/12 ad-hoc | 1 (value) / 2 (wire + repoint) | +| `--grad-brand` | Brand gradient for primary CTAs (redβ†’violet), matching the site | 1 (token) / 2 (Button) | +| `--surface-2` | Nested panels / table headers / inset rows | 1 | +| `--font-mono` | Code blocks, terminal, IDs, endpoints | 4 | +| `--text-muted` reconcile | Pick one value site↔panel (`#a1a1aa` recommended) | 2 | + +**D2 (text-muted):** site `#8b8b9e` vs dashboard `#a1a1aa`. Recommend keeping the **dashboard's `#a1a1aa`** and updating the _site_ to match. Cosmetic. + +--- + +## 6. Part C β€” Component standardization (Phases 2–4) + +Custom components (no shadcn/Radix), Tailwind v4, semantic tokens **mostly** adopted (195 files import the shared barrel). The work is removing the **bypasses**. Home: `src/shared/components/`. + +| # | Item | File(s) | Problem β†’ Target | Phase | +| --- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ----- | +| C1 | **Radius alignment** | `Button.tsx:14-18`, `Card.tsx:39`, `Modal.tsx`, `Input.tsx`, `Select.tsx` | mixed 6/8/12px β†’ `--radius`/`--radius-sm` (14/9) | 2 | +| C2 | **Button gradient + `accent` variant** | `Button.tsx:5-12` | primary is flat redβ†’red; align to `--grad-brand`; add missing `accent` variant. ~195 importers β€” highest visibility | 2 | +| C3 | **Tables** | `DataTable.tsx:122-176`, `logTableStyles.ts`, `globals.css:405-414` | 100% inline hardcoded rgba + non-existent vars; migrate to tokens, retire divergent styles | 3 | +| C4 | **Centralize status colors** | `flow/edgeStyles.ts`, `TokenHealthBadge.tsx`, `DegradationBadge.tsx`, `ProviderCascadeNode.tsx`, `Badge.tsx` + 5 helpers | 6+ copies of the same hex β†’ one module off `--color-success/warning/error` | 3 | +| C5 | **Card border** | `Card.tsx:39` | `border-white/5` β†’ brand `/8` | 2 | +| C6 | **Focus ring reconcile** βœ… DONE | `globals.css` `--focus-ring` (accent) vs form controls' `ring-primary/30` | unified on **accent (violet)** to match the global ring + disambiguate from the red error ring; error stays red | 4 | +| C7 | **Add `Checkbox` + `Textarea`** | raw ``/`