SQL Query Lab Evolution
A professional-grade SQL Server management studio running entirely in the browser — direct competitor to SSMS, Azure Data Studio, and dbForge. Formerly a SQL learning tool, now rearchitected for real database professionals managing real SQL Server infrastructure.
Core Value: Professionals can manage SQL Server infrastructure from any browser, with zero setup and full feature parity with desktop SSMS.
- Browser-based: Must work entirely in browser — no desktop app
- Cross-platform: Must work on macOS, Windows, Linux equally
- Backend required: Real SQL Server needs a proxy (cannot use sql.js for live connections)
- Professional audience: DBAs, developers — not learners
- JavaScript (ES2020+) - All application logic
- CSS - All styling (custom, no framework)
- Browser-only (client-side execution)
- No Node.js server component
- ES Modules via
<script type="module"> - None (no package.json or npm/yarn/pnpm)
- None (no bundler, no transpiler)
- Direct execution of ES modules in browser
- No build step required
- Vanilla JavaScript (no frontend framework)
- Custom module architecture (~13 JS files in
scripts/) - CodeMirror 5.65.16 (CDN) - SQL syntax highlighting and autocomplete
- sql.js 1.10.3 (CDN) - SQLite compiled to WebAssembly
- 8 CSS files in
styles/directory - CSS custom properties (variables) for theming
- BEM-like class naming convention
base.css- Variables, reset, global styles, splash screentopbar.css- Header/toolbar stylinglayout.css- Main app grid layouteditor.css- CodeMirror wrapper stylingresults.css- Query output panelright-panel.css- Question/sandbox panelsandbox.css- Sandbox mode specific stylesmodal.css- Question browser modal- Instrument Serif (headings)
- Figtree (body/sans)
- JetBrains Mono (code)
- No tsconfig, webpack, vite, rollup, etc.
- No environment setup required
- Runs directly from
index.html
main.js- Entry point, boot sequence, UI wiringruntime.js- Global state singleton (SQL engine, editor instance, databases)state.js- Persistent state (localStorage)db.js- Database operations layerui.js- DOM rendering functionseditor.js- CodeMirror initializationpractice.js- Practice mode logicsandbox.js- Sandbox mode logicquestions.js- Question data (hardcoded)seeds.js- Database seed data (SQL statements)format.js- SQL formatterutils.js- Utility functions
- Lowercase with underscores:
main.js,editor.js,sandbox.js,practice.js,ui.js,db.js,format.js,utils.js,state.js,runtime.js,questions.js,seeds.js - One concept per file;文件名 reflects primary export or responsibility
- camelCase:
runQuery,loadQuestion,setMode,saveDraft,persistSandboxDbDebounced - Verb-first naming:
runQuery,saveDraft,clearDraft,resetAllProgress - Predicate functions:
isMutating,compareResultsreturning boolean-like objects - camelCase:
currentMode,currentQuestionId,editorLoading - Suffix
_dbfor database instances:sandboxDb,pristineDb,liveDb - Suffix
_timerfor timer IDs:_sandboxSaveTimer,_persistTimer - Prefix
_for module-scoped "private" locals:_hooks,_sandboxSaveTimer - UPPER_SNAKE_CASE:
MAX_HISTORY,STORAGE_KEY,LEGACY_SOLVED_KEY - Plain objects used as namespaces:
runtime.cursor,runtime.sandboxDb - exported arrays as data:
QUESTIONS,SEEDS
- No explicit formatter configured (no Prettier, ESLint, or biome config found)
- 2-space indentation observed
- Single quotes for strings in JS:
'practice','sandbox','mssql' - Semicolons at statement ends
- No ESLint configuration detected
- No pre-commit hooks detected
- No path aliases configured; all imports use relative
./paths
- ES Modules (
<script type="module">in index.html) - Named exports only; no default exports observed
- Barrel re-exports through parent modules (e.g.,
state.jsre-exports from./runtime.js) - Single mutable
stateobject exported fromstate.js— acts as the application store solvedis aSetderived fromstate.solvedfor O(1) lookups- Persistence via
localStoragewith keyquerylab:v1 - Debounced writes via
persist()with 400ms timer - Uses a hook-injection pattern:
setDbHooks({ showFeedback, switchTab, renderSchema }) - Hooks called via
_hooks.showFeedback(...)rather than direct imports main.jswires hooks after all modules are importedtry/catchfor recoverable operations (DB restores, localStorage reads)console.warn()for recoverable errors (corrupt state, quota exceeded)console.error()for critical/unexpected errors- Errors surfaced to UI via
showFeedback('error', ...)ortoast(...) - Direct DOM queries:
document.getElementById(),document.querySelectorAll() - No virtual DOM or framework
- Event delegation used in
main.jsfor menu and tab buttons - Template literals for HTML generation in render functions
async/boot()for initialization (sql.js loading)- No
awaitinside modules at top level — all async contained inboot() - No Promises exposed externally
base.css— CSS custom properties (:root), reset, layout shell, typography, scrollbarseditor.css— CodeMirror editor styling, autocomplete popuplayout.css— Grid layout for.app,.main,.left,.right,.center,.resultsmodal.css— Question browser modalresults.css— Result tables, feedback, output panelsright-panel.css— Left panel tabs, schema viewer, history, resourcessandbox.css— Snippet list, snippet rowtopbar.css— Top bar, mode buttons, DB select- kebab-case:
.editor-wrap,.schema-table,.results-tab,.feedback,.toast - BEM-inspired compound names:
.left-tab[data-left],.results-tab[data-tab] - Semantic class names:
.splash,.logo,.progress-fill - Each CSS file targets one UI component/section
- No CSS modules — all classes are global
- Not used; no JSDoc annotations detected
- Functions are focused — typically 20-80 lines
- Large functions broken into smaller helpers (e.g.,
compareResultsis separate fromrunPracticeQuery) - Object destructuring for hook configs:
setDbHooks({ showFeedback, switchTab, renderSchema }) - Multiple primitive params when clarity demands:
navQuestion(delta) - Most functions return
voidand mutate shared state directly - Pure utility functions return values:
escapeHtml(s),formatSql(raw),normalizeCell(v)
- ES module-based modular architecture with explicit imports between modules
- Runtime singleton pattern for shared state (
runtime.js) - Persistent state via localStorage with debounced saves
- Three distinct operating modes: Practice, Sandbox, MS SQL Translation
- Client-side only — no backend, all databases live in browser memory via sql.js
- Purpose: Render interface, handle user input and DOM events
- Location:
index.html,scripts/ui.js,scripts/editor.js - Contains: DOM rendering functions, event listeners wired in
main.js - Depends on:
runtime.js(for editor, cursor state),state.js(for persisted data) - Used by: User interactions trigger handlers in
main.jswhich call other modules - Purpose: Execute SQL against in-browser SQLite databases
- Location:
scripts/sandbox.js,scripts/practice.js,scripts/db.js - Contains: Query running, validation logic, sandbox/practice mode differentiation
- Depends on:
runtime.js(SQL, liveDb, sandboxDb),state.js(persistence) - Used by:
main.jsrun handlers, editor keyboard shortcuts - Purpose: Manage sql.js instances, pristine seeds, and sandbox state persistence
- Location:
scripts/db.js,scripts/seeds.js - Contains: Database initialization, cloning from pristine, base64 serialization for localStorage
- Depends on:
runtime.js(SQL singleton),state.js(bytes helpers) - Used by:
main.js(boot),practice.js,sandbox.js - Purpose: Persist drafts, history, solved questions, and sandbox DB states to localStorage
- Location:
scripts/state.js - Contains: Load/save state, draft management, history, progress tracking
- Depends on: localStorage
- Used by: All modules that need persisted state
- Purpose: Define practice questions with validation logic
- Location:
scripts/questions.js,scripts/seeds.js - Contains: 74 practice questions across 3 databases (hospital, company, school)
- Used by:
practice.js,main.js(question list rendering) - Purpose: Shared helpers for formatting, escaping, SQL parsing
- Location:
scripts/format.js,scripts/utils.js - Contains: SQL formatter, HTML escaper, SQL syntax highlighter, multi-statement splitter
- Used by:
ui.js,editor.js,sandbox.js,practice.js
- Load questions from
QUESTIONS[]with reference queries and verification queries - Clone pristine DB fresh per question, apply optional
setupQuery - Compare user result against reference using
compareResults()(result-set or state-based) - Mark solved, persist drafts, show pass/fail feedback
- Mutable in-memory DB that persists to localStorage as base64
- Run any SQL (SELECT, INSERT, UPDATE, DELETE, DDL)
- Multi-statement scripts supported via
splitSqlStatements() - Snippet save/load functionality
- Static translation of SQLite SQL syntax to T-SQL
- Regex-based replacements for type names, functions, LIMIT/OFFSET pagination
- Output shown in right panel, does not execute
- State auto-saves to localStorage via
persist()with 400ms debounce - Sandbox DBs serialize as base64 after 500ms debounce to avoid quota issues
- On load, sandbox DBs restored from
state.sandboxStatesif present
SQL— sql.js singleton set after booteditor— CodeMirror instanceliveDb— Practice mode DB instances (per database name)sandboxDb— Sandbox mode DB instances (per database name)pristineDb— Seed byte arrays for cloning (never mutated)cursor— Mutable session state:currentMode,currentDbName,currentQuestionId,lastUserResult,lastExpectedResult, etc.solved[]— Array of solved question IDsdrafts{}— Draft SQL per question IDhistory[]— Last 20 query runs with SQL, timestamp, success/failuresnippets[]— Named saved SQL snippetssandboxStates{}— Base64-encoded sandbox DB states per databasemode,sandboxDb,sandboxScript,lastQuestionId,lastCategoryFilter,lastDifficultyFiltermain.jswires cross-module hooks viasetDbHooks()andsetUiHooks()after all modules importdb.jsreceives UI callbacks without importingui.jsui.jsreceivesloadQuestion/loadHistoryItemcallbacks without importingpractice.js/sandbox.js
activeDb()indb.js— returns eithersandboxDborliveDbbased on current modecloneFromPristine(name)— creates fresh in-memory DB from seed bytesinitEditor({ runQuery, runMssqlTranslation })ineditor.js— creates CodeMirror, wires hints- All editor access via
runtime.editorafter initialization runtime.cursor.editorLoadingflag prevents change-handler loops when programmatically setting valuesetMode(mode)insandbox.js— single entry point for switching modes- Sets CSS classes on
document.bodyfor mode-specific styling - Calls
enterSandbox(),enterMssql(), orenterPractice()accordingly compareResults(a, b)— deep-compares result sets column-by-column, row-by-rownormalizeCell()— converts numbers and nulls to comparable strings- Two validation types:
result(compares SELECT output) andstate(compares DB state via verificationQuery)
- Boot function initializes sql.js, seeds databases, wires UI, sets initial mode
- All button/menu handlers defined here, delegating to specialized modules
- Single
boot()async function called at module load time - Loads CodeMirror and sql.js from CDN (global scripts)
- Loads
scripts/main.jsas ES module (entry point) - Contains all HTML structure; no server-side rendering
- SQL errors caught in try/catch, displayed via
showFeedback('error', ...) - localStorage failures caught with
try/catch, logged to console.warn - Missing questions/databases return early (no-op)
- Boot errors render error message in splash div
compareResults()returns{ ok: false, reason: '...' }with specific mismatch description- User gets column count, row count, or cell-level diff feedback
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
/gsd:quickfor small fixes, doc updates, and ad-hoc tasks/gsd:debugfor investigation and bug fixing/gsd:execute-phasefor planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
Profile not yet configured. Run
/gsd:profile-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.