docs: spec, implementation plan, jev goal gate
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
build
|
||||||
|
data
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env*.local
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
|
.vercel
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# Design System Master File
|
||||||
|
|
||||||
|
> **LOGIC:** When building a specific page, first check `design-system/pages/[page-name].md`.
|
||||||
|
> If that file exists, its rules **override** this Master file.
|
||||||
|
> If not, strictly follow the rules below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Project:** SQL Server qAI
|
||||||
|
**Generated:** 2026-09-21 12:56:55
|
||||||
|
**Category:** Developer Tool / IDE
|
||||||
|
**Design Dials:** Variance 4/10 (Balanced / Modern) | Motion 3/10 (Subtle) | Density 8/10 (Dense / Dashboard)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Global Rules
|
||||||
|
|
||||||
|
### Color Palette
|
||||||
|
|
||||||
|
| Role | Hex | CSS Variable |
|
||||||
|
|------|-----|--------------|
|
||||||
|
| Primary | `#1E293B` | `--color-primary` |
|
||||||
|
| On Primary | `#FFFFFF` | `--color-on-primary` |
|
||||||
|
| Secondary | `#334155` | `--color-secondary` |
|
||||||
|
| On Secondary | `#FFFFFF` | `--color-on-secondary` |
|
||||||
|
| Accent/CTA | `#22C55E` | `--color-accent` |
|
||||||
|
| On Accent/CTA | `#0F172A` | `--color-on-accent` |
|
||||||
|
| Background | `#0F172A` | `--color-background` |
|
||||||
|
| Foreground | `#F8FAFC` | `--color-foreground` |
|
||||||
|
| Card | `#1B2336` | `--color-card` |
|
||||||
|
| Card Foreground | `#F8FAFC` | `--color-card-foreground` |
|
||||||
|
| Muted | `#272F42` | `--color-muted` |
|
||||||
|
| Muted Foreground | `#94A3B8` | `--color-muted-foreground` |
|
||||||
|
| Border | `#475569` | `--color-border` |
|
||||||
|
| Destructive | `#EF4444` | `--color-destructive` |
|
||||||
|
| On Destructive | `#000000` | `--color-on-destructive` |
|
||||||
|
| Ring | `#FFFFFF` | `--color-ring` |
|
||||||
|
|
||||||
|
**Color Notes:** Code dark + run green
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
- **Heading Font:** JetBrains Mono
|
||||||
|
- **Body Font:** IBM Plex Sans
|
||||||
|
- **Mood:** code, developer, technical, precise, functional, hacker
|
||||||
|
- **Google Fonts:** [JetBrains Mono + IBM Plex Sans](https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap)
|
||||||
|
|
||||||
|
**CSS Import:**
|
||||||
|
```css
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spacing Variables
|
||||||
|
|
||||||
|
*Density: 8/10 — Dense / Dashboard*
|
||||||
|
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| `--space-xs` | `2px` / `0.125rem` | Tight gaps |
|
||||||
|
| `--space-sm` | `4px` / `0.25rem` | Icon gaps, inline spacing |
|
||||||
|
| `--space-md` | `8px` / `0.5rem` | Standard padding |
|
||||||
|
| `--space-lg` | `12px` / `0.75rem` | Section padding |
|
||||||
|
| `--space-xl` | `16px` / `1rem` | Large gaps |
|
||||||
|
| `--space-2xl` | `24px` / `1.5rem` | Section margins |
|
||||||
|
| `--space-3xl` | `32px` / `2rem` | Hero padding |
|
||||||
|
|
||||||
|
### Shadow Depths
|
||||||
|
|
||||||
|
| Level | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle lift |
|
||||||
|
| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons |
|
||||||
|
| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns |
|
||||||
|
| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Specs
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Primary Button */
|
||||||
|
.btn-primary {
|
||||||
|
background: #22C55E;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secondary Button */
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: #1E293B;
|
||||||
|
border: 2px solid #1E293B;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
```css
|
||||||
|
.card {
|
||||||
|
background: #0F172A;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: all 200ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
```css
|
||||||
|
.input {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: border-color 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
border-color: #1E293B;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px #1E293B20;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
```css
|
||||||
|
.modal-overlay {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Style Guidelines
|
||||||
|
|
||||||
|
**Style:** Dark Mode (OLED)
|
||||||
|
|
||||||
|
**Keywords:** Dark theme, low light, high contrast, deep black, midnight blue, eye-friendly, OLED, night mode, power efficient
|
||||||
|
|
||||||
|
**Best For:** Night-mode apps, coding platforms, entertainment, eye-strain prevention, OLED devices, low-light
|
||||||
|
|
||||||
|
**Key Effects:** Minimal glow (text-shadow: 0 0 10px), dark-to-light transitions, low white emission, high readability, visible focus
|
||||||
|
|
||||||
|
### Page Pattern
|
||||||
|
|
||||||
|
**Pattern Name:** FAQ/Documentation Landing
|
||||||
|
|
||||||
|
- **Conversion Strategy:** Reduce support tickets. Track search analytics. Show related articles. Contact escalation path.
|
||||||
|
- **CTA Placement:** Search bar prominent + Contact CTA for unresolved questions
|
||||||
|
- **Section Order:** Hero with search bar > Popular categories > FAQ accordion > Contact/support CTA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motion
|
||||||
|
|
||||||
|
**Scroll Reveal** (Subtle) — Trigger: scroll (viewport enter) | Duration: 300-400ms | Easing: `power1.out`
|
||||||
|
|
||||||
|
```js
|
||||||
|
gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });
|
||||||
|
```
|
||||||
|
|
||||||
|
**Framework notes:** Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger); Use matchMedia('(prefers-reduced-motion: reduce)') to skip non-essential motion and render the final state immediately
|
||||||
|
|
||||||
|
- ✅ Keep the y offset small (8-16px) so it reads as a fade, not a slide
|
||||||
|
- ❌ Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback
|
||||||
|
- ⚡ toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-Patterns (Do NOT Use)
|
||||||
|
|
||||||
|
- ❌ Light mode default
|
||||||
|
- ❌ Slow performance
|
||||||
|
|
||||||
|
### Additional Forbidden Patterns
|
||||||
|
|
||||||
|
- ❌ **Emojis as icons** — Use SVG icons (Heroicons, Lucide, Simple Icons)
|
||||||
|
- ❌ **Missing cursor:pointer** — All clickable elements must have cursor:pointer
|
||||||
|
- ❌ **Layout-shifting hovers** — Avoid scale transforms that shift layout
|
||||||
|
- ❌ **Low contrast text** — Maintain 4.5:1 minimum contrast ratio
|
||||||
|
- ❌ **Instant state changes** — Always use transitions (150-300ms)
|
||||||
|
- ❌ **Invisible focus states** — Focus states must be visible for a11y
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Delivery Checklist
|
||||||
|
|
||||||
|
Before delivering any UI code, verify:
|
||||||
|
|
||||||
|
- [ ] No emojis used as icons (use SVG instead)
|
||||||
|
- [ ] All icons from consistent icon set (Heroicons/Lucide)
|
||||||
|
- [ ] `cursor-pointer` on all clickable elements
|
||||||
|
- [ ] Hover states with smooth transitions (150-300ms)
|
||||||
|
- [ ] Light mode: text contrast 4.5:1 minimum
|
||||||
|
- [ ] Focus states visible for keyboard navigation
|
||||||
|
- [ ] `prefers-reduced-motion` respected
|
||||||
|
- [ ] Responsive: 375px, 768px, 1024px, 1440px
|
||||||
|
- [ ] No content hidden behind fixed navbars
|
||||||
|
- [ ] No horizontal scroll on mobile
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
|||||||
|
# SQL Server qAI — Spec
|
||||||
|
|
||||||
|
**Date:** 2026-09-21
|
||||||
|
**Owner:** ahmedmohammed
|
||||||
|
**Status:** agreed in chat, this file is the spec of record
|
||||||
|
|
||||||
|
## Goal (verbatim from the owner)
|
||||||
|
|
||||||
|
> portal that take xml of sql server query plan and use power of generative ai to
|
||||||
|
> analysis provide multiple action steps, multiple query rewrite if possible,
|
||||||
|
> Jev then rank and decide.
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
|
||||||
|
A containerised web tool. A DBA or consultant logs in, pastes a SQL Server
|
||||||
|
execution plan (Showplan XML, or a `.sqlplan` file), and gets back:
|
||||||
|
|
||||||
|
1. What the plan says is wrong (bottleneck, missing indexes, spills, bad row guesses).
|
||||||
|
2. **Multiple** candidate action plans — several different remediations, not one.
|
||||||
|
3. **Multiple** candidate SQL rewrites where a rewrite is possible.
|
||||||
|
4. A **Jev**-ranked verdict: which candidate to run first, with typed probabilities
|
||||||
|
and confidence, and why.
|
||||||
|
5. One final "best fast path": the recommended SQL and the steps to apply it.
|
||||||
|
6. The result rendered as **structured UI elements** (OpenUI Lang → React), not a
|
||||||
|
wall of markdown. Export to PDF, copy to clipboard.
|
||||||
|
|
||||||
|
The demo value is Jev as the decision-maker: the generative model proposes,
|
||||||
|
**Jev disposes**. Every ranking number on screen is a typed judgment from
|
||||||
|
`POST /v1/systemone`, not a string the LLM wrote.
|
||||||
|
|
||||||
|
## Product surface
|
||||||
|
|
||||||
|
| Screen | Job |
|
||||||
|
| --- | --- |
|
||||||
|
| `/signup`, `/login` | Email + password. Nothing else. |
|
||||||
|
| `/setup` | First-run guide: 3 steps — analyst model, Jev, done. |
|
||||||
|
| `/app` | Left rail = previous analyses (rename, delete). Main = paste plan → run → live report. |
|
||||||
|
| `/settings` | Update analyst model (base URL, key, model, extra params), Jev (key, model), email/password, history. |
|
||||||
|
|
||||||
|
Report actions: **Export to PDF**, **Copy**. History actions: **rename**, **delete**.
|
||||||
|
|
||||||
|
## Actors (two models, strict roles)
|
||||||
|
|
||||||
|
- **Analyst** — any OpenAI-compatible endpoint the user configures (`base_url`,
|
||||||
|
`key`, `model`, extra params). Generates: candidate action plans, candidate SQL
|
||||||
|
rewrites, and the final OpenUI Lang report. Text in, text out.
|
||||||
|
- **Jev** — TypeSafe System One (`@typesafe-ai/sdk`, model `jev-latest`). Never
|
||||||
|
generates prose. Answers typed questions (`score`, `choice`, `noul`) about each
|
||||||
|
candidate and returns probabilities + confidence. Code combines them.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Auth: signup, login, logout, session survives reload. Per-user data isolation.
|
||||||
|
2. Credentials at rest are encrypted (AES-256-GCM, `APP_SECRET` env). Never echoed
|
||||||
|
to the browser — masked (`••••1234`).
|
||||||
|
3. Plan input: paste XML, or drop/select a `.sqlplan`/`.xml` file. Clear inline
|
||||||
|
error if it is not a ShowPlanXML document.
|
||||||
|
4. Deterministic digest first, model second: parse the XML in code (operators,
|
||||||
|
costs, missing indexes, warnings, waits, spills, estimate vs actual). The model
|
||||||
|
sees the digest, never 40 MB of XML.
|
||||||
|
5. 2–4 candidates per run. Each candidate: diagnosis, ordered actions with effort
|
||||||
|
and change-control flag, optional rewrite SQL, expected effect.
|
||||||
|
6. Jev judges every candidate in parallel on 4 dimensions, then a second call picks
|
||||||
|
`first_to_run` across all candidates. Composite weights live in code, visible in UI.
|
||||||
|
7. Safety gate: a candidate Jev rates as semantically unsafe is never presented as
|
||||||
|
the recommended path without a visible "verify results before deploying" flag.
|
||||||
|
8. History: every run persisted (input digest, candidates, Jev verdict, rendered
|
||||||
|
report). Reopening a history item replays the stored report — zero model calls.
|
||||||
|
9. Streaming: stage indicator while digesting/proposing/judging, then the report
|
||||||
|
renders token-by-token. No spinner-for-30-seconds.
|
||||||
|
10. Single container: `docker compose up` → one image, one `/data` volume, no
|
||||||
|
external DB, no queue, no Redis.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- No live connection to SQL Server. No plan-from-cache lookup. No automatic index
|
||||||
|
creation. No multi-user teams, roles, or sharing. No billing. No mobile app.
|
||||||
|
No markdown editor. No prompt marketplace. No agentic loop with tool calls.
|
||||||
|
|
||||||
|
## Success looks like
|
||||||
|
|
||||||
|
Paste a real slow-query plan → under 60 s → a ranked set of remediations where the
|
||||||
|
top pick is defensible because Jev's probabilities are on screen, and the winning
|
||||||
|
SQL is one click from the clipboard.
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// TYPESAFE_API_KEY=... node scripts/jev-gate.mjs [path-to-plan.md]
|
||||||
|
// One Jev call: does this plan actually build the goal? Prints YES or NO. No deps, no framework.
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
|
const key = process.env.TYPESAFE_API_KEY ?? process.env.JEV_API_KEY;
|
||||||
|
if (!key) { console.error("set TYPESAFE_API_KEY"); process.exit(1); }
|
||||||
|
|
||||||
|
const file = process.argv[2] ?? "docs/superpowers/plans/2026-09-21-sql-server-qai.md";
|
||||||
|
const raw = readFileSync(file, "utf8");
|
||||||
|
// Judge the plan's structure, not its volume: code blocks are ~90% of the bytes and 60k of them
|
||||||
|
// would silently drop the last third of the plan. Keep headings, steps, prose, and checkbox lines.
|
||||||
|
const plan = process.argv.includes("--full")
|
||||||
|
? raw.slice(0, 90_000)
|
||||||
|
// Judge structure, not volume: code blocks are ~2/3 of the bytes. Replace each one with a marker
|
||||||
|
// but keep every prose line — filtering prose down to headings hid half the plan from the model.
|
||||||
|
: raw.replace(/```[\s\S]*?```/gs, "[code]")
|
||||||
|
.replace(/\n{2,}/g, "\n").slice(0, 90_000);
|
||||||
|
|
||||||
|
const goal = `A portal that takes the XML of a SQL Server query plan, uses generative AI to analyse it, provides
|
||||||
|
multiple action steps and multiple query rewrites where possible, and then has Jev rank the options and decide the
|
||||||
|
best one, returning the best query too. Output is rendered with OpenUI elements, with copy and export-to-PDF.`;
|
||||||
|
|
||||||
|
const res = await fetch("https://api.typesafe.ai/v1/systemone", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: "jev-latest",
|
||||||
|
state: { goal, plan },
|
||||||
|
questions: {
|
||||||
|
matches_goal: {
|
||||||
|
type: "noul",
|
||||||
|
instructions: "Would software built exactly as described in `plan` satisfy the goal in `goal`?",
|
||||||
|
criteria: {
|
||||||
|
true: "Every part of the goal is built by some task in the plan.",
|
||||||
|
false: "A part of the goal is missing, or replaced by something else.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coverage: {
|
||||||
|
type: "score",
|
||||||
|
instructions: "How completely does `plan` cover each separate part of the goal in `goal`?",
|
||||||
|
criteria: [
|
||||||
|
"Most of the goal is absent from the plan.",
|
||||||
|
"The happy path only; failures and edge cases missing.",
|
||||||
|
"Most parts, with one thin area.",
|
||||||
|
"Every part has a task with a test.",
|
||||||
|
"Every part has a task, a test, and a named failure behaviour.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
biggest_gap: {
|
||||||
|
type: "choice",
|
||||||
|
instructions: "For the user to actually reach the goal in `goal`, which single option listed here does `plan` still fail to cover? Choose nothing only if every part of the goal is built and tested by some task.",
|
||||||
|
criteria: {
|
||||||
|
nothing: "Nothing is missing: every part of the goal is built and tested by some task.",
|
||||||
|
get_plan: "Getting a real .sqlplan out of SQL Server and into the portal.",
|
||||||
|
jev_key: "Obtaining and entering their own Jev (TypeSafe) API key in the settings panel.",
|
||||||
|
analyst_endpoint: "Knowing which analyst model endpoint to point the app at.",
|
||||||
|
openui_render: "Rendering the answer as OpenUI elements rather than text.",
|
||||||
|
copy_pdf: "Copying the output or exporting it to PDF.",
|
||||||
|
jev_decides: "Jev ranking the options and deciding which one runs first.",
|
||||||
|
multiple_options: "Several genuinely different action plans and query rewrites.",
|
||||||
|
trust: "Telling the user whether to believe the ranking on their own data.",
|
||||||
|
failures: "What happens when the XML, the analyst model, or Jev is broken.",
|
||||||
|
history: "History with rename and delete, and reopening a stored run.",
|
||||||
|
container: "Running the whole thing in one container.",
|
||||||
|
qa: "End-to-end browser QA of the finished app.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) { console.error(`Jev HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`); process.exit(1); }
|
||||||
|
const { answers: a, model } = await res.json();
|
||||||
|
|
||||||
|
const yes = a.matches_goal.noul >= 0.5 && a.coverage.score >= 3 && a.biggest_gap.choice === "nothing";
|
||||||
|
console.log(model, "→", yes ? "YES — this plan builds your goal" : "NO — a gap needs closing first");
|
||||||
|
console.table({
|
||||||
|
"matches_goal (p yes)": a.matches_goal.noul,
|
||||||
|
"coverage (0-4)": a.coverage.score,
|
||||||
|
"coverage confidence": a.coverage.confidence,
|
||||||
|
"biggest_gap": a.biggest_gap.choice,
|
||||||
|
"gap confidence": a.biggest_gap.confidence,
|
||||||
|
});
|
||||||
|
console.log(Object.entries(a.biggest_gap.probabilities).sort((x, y) => y[1] - x[1]).slice(0, 4));
|
||||||
Reference in New Issue
Block a user