This project is a static browser app organized as feature-oriented ES modules. The architecture is intentionally buildless so it can be served by any static web server, but it still uses explicit module loading, state facades, browser smoke tests, and architecture fitness checks.
- It solves a real workflow with complex state, async execution, virtualized data display, export, overlays, and editable forms.
- It uses native browser ES modules and declares
"type": "module"for Node-side tooling. - It has executable guardrails for module specifiers, forbidden browser globals, query-state access, module reachability, import cycles, layer boundaries, coupling/modularity budgets, folder cohesion, Git-history change coupling, cognitive complexity, unit-tested business logic, and browser smoke behavior.
- Query history is split into grouping/search, backend status mapping, request mapping, row rendering, detail rendering, view metadata, and coordinator modules.
- Query templates are split into repository, payload, collection, state, category-view, list-view, and coordinator modules.
- It documents the intended module boundaries and known legacy areas.
The former private runtime coordination layer has been removed. Feature coordination now goes through ES imports plus explicit service/action registration.
Current product status and remaining roadmap items are tracked in docs/ROADMAP.md.
The canonical application source root is src/.
The repository intentionally rejects a mixed top-level JavaScript source layout. Top-level files are for static hosting, runtime entry files, tooling, docs, tests, scripts, and config:
index.htmlis the static host entrypoint.backgroundNotificationServiceWorker.jsstays at the root because browsers register service workers by served scope.cache-bust.json,package.json,eslint.config.cjs,.github/,config/,docs/,scripts/, andtests/stay at the root as project/runtime/tooling files.- Browser application modules, feature logic, UI systems, and stylesheet source live under
src/.
New app code should be added under src/core/, src/features/, src/lib/, src/ui/, or src/styles/ according to the ownership rules below. Reusable frontend surfaces that are intended for other websites should be exposed under src/components/ as public entrypoints over reusable src/lib/ internals. Tests and scripts should import app modules from src/; they should not create a parallel top-level source tree.
index.htmlloads vendor scripts, fetchescache-bust.jsonwithcache: no-store, versions the app stylesheet/module entry, and registers the root service worker for same-origin app assets.index.htmldynamically importssrc/appModules.jswith the current cache version.src/appModules.jsimports feature modules in deterministic startup order.src/core/bootstrap.jsinitializes DOM-bound systems after DOM readiness.- User actions write query state through
QueryChangeManager. - UI reads query state through
QueryStateReadersand subscriptions. src/core/queryExecution.jsbuilds the backend payload, runs or cancels work, then updates result state.- Table, history, filters, templates, and overlays render from the current state and service facades.
src/core/backendApi.js currently points at an example/testing query API. That endpoint exists to demonstrate the integration shape and gives the browser smoke test a stable route to stub. It should not be treated as the live site's long-term production backend.
The intended deployment model is bring-your-own API. The public live site should remove project-owned API usage and let each deployment provide its own compatible API URLs/configuration for field metadata, query execution, status/cancel, history result loading, and template persistence. Users can configure a compatible endpoint from the API Settings panel. Static deployments can also supply ?api_url=... or ?query_api_url=...; valid values are stored in localStorage under query-project.api-url. Local deployments can still change the default in src/core/backendApi.js when that is simpler.
Result hydration uses the canonical streaming JSONL contract. src/core/queryStream.js reads newline-delimited meta, row, progress, warning, error, and done events, then src/core/queryResultParser.js maps row value arrays into displayed columns. JSON array values are preserved as array-backed multi-value cells so table rendering, split columns, post filters, and Excel export share one representation.
The full backend integration contract is documented in docs/INTEGRATION.md.
| Layer | Path | Responsibility |
|---|---|---|
| Entry/bootstrap | src/appModules.js, src/core/bootstrap.js |
Module loading and app startup |
| State | src/core/queryState.js |
Query state, lifecycle flags, read/write facades |
| Services/actions | src/core/appServices.js, src/core/appUiActions.js |
Cross-feature coordination without direct feature coupling |
| Core utilities | src/core/fieldDefs.js, src/core/formatting/, src/core/*Utils.js, src/core/textMeasurement.js |
Backend-driven field registry, focused helpers, and shared primitives imported from their owning modules instead of a mixed utility facade |
| Reusable components | src/components/ |
Public ES module entrypoints for the reusable mounted virtual table, table projection, column drag/drop, workbook export, date input, and tooltip behavior |
| Reusable library internals | src/lib/ |
Framework-free virtual-table, drag/drop, and workbook-export primitives that can be used by app features, public components, tests, and scripts without depending on app state |
| Data contract | src/core/fieldDefs.js, src/features/filters/queryPayload.js |
Backend field metadata registry, payload generation, and filter normalization |
| Features | src/features/filters/, src/features/table/, src/features/history/, src/features/templates/ |
User workflows grouped by product feature, with complex widgets split into focused view/helper modules |
| Query history | src/features/history/ |
History shell split from request mapping, config loading, result hydration, row rendering, grouping, notifications, tooltips, and status mapping |
| Filter workflows | src/features/filters/ |
Backend payload contracts, field metadata, condition validation, buildable-field construction, side-panel rendering, and side-panel reorder actions |
| Template workflows | src/features/templates/ |
Template shell split from models, state, repository, payloads, category actions/views, list/detail rendering, and view-state helpers |
| UI feature folders | src/ui/form-mode/, src/ui/field-picker/ |
Larger UI workflows with dedicated shell, field-picker, query-preview, state helper, presentation, and interaction modules |
| Table feature folders | src/features/table/drag-drop/, src/features/table/virtual-table/, src/features/table/post-filters/, src/features/table/export/ |
App-specific result-table workflows grouped by behavior, with stateful drag/drop, resize, header-action, post-filter, context-menu, and export overlay coordination split from reusable src/lib/ primitives |
| Shared UI | src/ui/ |
App shell rendering, shared selectors, modals, toasts, tooltips, date picker, field picker, and form mode |
| Styles | src/styles/tokens.css, src/styles/app.css, plus feature CSS files |
Token-driven theming, feature-scoped styling, and a single stylesheet entry |
| Architecture config | config/ |
Forbidden browser globals, module budgets, and import-boundary rules |
| Tests | tests/architecture/, tests/unit/, tests/browser/ |
Architecture checks, focused unit coverage, and browser smoke coverage |
src/owns the browser application source, keeping the repository root focused on static-host files, runtime files, tooling, docs, and tests.src/components/owns public reusable entrypoints. These modules wrap stablesrc/lib/internals for outside sites and future package exports; they should stay small and avoid importing app-shell coordinators.src/core/owns app-wide state, services/actions, lifecycle, backend/query execution, the backend-driven field metadata registry, browser primitives, startup glue, and shared formatting helpers undersrc/core/formatting/.src/lib/owns reusable, framework-free implementation internals. It can importsrc/core/primitives and othersrc/lib/modules, but it cannot import app features, app UI shells, query state stores, or overlay coordinators.src/features/owns product features: filters, history, table, and templates.src/features/table/keeps the top-level table surface incontextMenu.jsand groups app-specific table workflows intodrag-drop/,export/,post-filters/, andvirtual-table/.src/features/table/export/owns the app export dialog, progress UI, split-column preference UI, and download orchestration. Worker-safe workbook building and ZIP/XLSX generation live insrc/lib/workbook-export/.src/lib/workbook-export/keeps workbook data shaping, overview/details sheets, browser-safe Blob generation, worker entrypoint support, and ZIP writing separate from app UI orchestration.src/lib/virtual-table/owns reusable virtualizer, column layout, scrollbar controller, row projection, sort, split-column transforms, multi-value display helpers, and duplicate-row collapse.src/lib/drag-drop/owns reusable drag/drop math, viewport auto-scroll, resize-start detection, and drop-anchor layout helpers.src/features/filters/owns filter payload shaping, field warning metadata, side-panel rendering, and side-panel reorder actions. Shared query filter editing lives insrc/ui/filter-editor/, while form-mode field selection lives insrc/ui/field-picker/andsrc/ui/form-mode/.src/ui/keeps shared UI systems at the root and groups larger workflows infield-picker/andform-mode/.src/styles/owns design tokens, the stylesheet entrypoint, and feature CSS.
The theme system follows a token layering model: reference tokens define raw values, semantic system tokens define UI roles, and component CSS consumes those roles. src/styles/tokens.css is the source of truth for those tokens and is imported before all feature styles from src/styles/app.css.
Rules for theme work:
- Add new visual modes by overriding
--qp-sys-*semantic tokens under root attributes such asdata-theme-resolved,data-theme-accent, ordata-theme-contrast. - Keep feature CSS focused on component structure and state. Prefer semantic tokens such as
--qp-sys-color-surface,--qp-sys-color-on-surface,--qp-sys-color-outline, and--qp-sys-color-focus-ringover hardcoded light/dark values. --theme-*variables are compatibility aliases for older component rules. They must resolve to--qp-sys-*tokens and should not gain new raw values.- Contrast-sensitive states such as borders, focus rings, selected rows, and icon-only controls must be tokenized so they can satisfy light, dark, and higher-contrast variants together.
The contract is executable in tests/architecture/themeTokenContract.mjs, which verifies token import order, required semantic roles, light/dark variants, contrast support, future accent axes, and alias ownership.
Reusable components are documented in docs/COMPONENTS.md and exported through src/components/index.js.
Current public surfaces:
src/components/virtual-table/: mounted DOM virtual-table component with packaged scrollbar, headless virtual-table projection, bounded render-window planning, split-column transforms, duplicate-row collapse, table sorting, scrollbar controller, and column-layout helpers.src/components/drag-drop/: headless column drag/drop controller plus reusable drop-anchor, auto-scroll, viewport, and resize-target helpers.src/components/workbook-export/: custom XLSX Blob/download generation, grouping helpers, workbook details, and overview helpers.src/components/date-picker/: DOM-bound date input enhancement plus shared date parsing/normalization helpers.src/components/tooltips/: browser-safe tooltip behavior plus field/filter tooltip HTML formatters.
Rules for this surface:
- Outside sites should import from
src/components/, not fromsrc/features/. - Component entrypoints may wrap
src/lib/internals, but they should not depend on query-state stores, app bootstrap, app shell rendering, feature coordinators, or overlay coordinators. - Public component behavior should have focused coverage in
tests/unit/components/. - If a component starts needing framework-specific mounting, add that as an adapter around the public entrypoint instead of mixing it into the feature internals.
The project no longer exposes application APIs through window.*. Startup readiness is represented by the data-query-app-modules-ready attribute on the document root and the query-app:modules-ready DOM event from src/appModules.js.
Rules now enforced:
- New
window.foo = ...exports fail lint and architecture checks. - New
Object.defineProperty(window, 'foo', ...)exports fail lint and architecture checks. - Reads from former app-level
window.*bridge names fail architecture checks. - Query lifecycle state must be read through
QueryStateReaders, not through loose globals. - App modules cannot use CommonJS.
- Module imports cannot include cache-busting query strings.
This removes the public browser-global API surface and keeps cross-feature coordination explicit.
The checked-in cache-bust.json manifest is generated by npm run cache:bust from the source HTML, JavaScript, and CSS assets that the static app serves. npm test runs npm run cache:bust:check, so CI fails when app assets change without an updated manifest.
Source modules still use clean ES import specifiers. Cache keys are applied at the runtime loader boundary and by the root service worker, which fetches same-origin app assets with cache: reload. This avoids scattering query strings through the module graph while still preventing stale deployed modules after a push.
tests/architecture/architectureFitness.mjs builds a static graph from ES imports and enforces these constraints:
- Every application module must be reachable from
src/appModules.js, a configured public component entrypoint, or a worker entrypoint. - Local imports must resolve to explicit
.jsmodules inside the application source set. - Production package dependencies must be imported by application modules; unused runtime packages fail the gate.
- Imports must follow the layer rules in
config/architectureRules.cjs. - Circular imports fail the architecture gate.
- Legacy large modules cannot grow beyond their current budget.
The architecture rules are executable, versioned, and run in CI.
tests/architecture/couplingModularityFitness.mjs adds a metrics-oriented gate over the same source graph. It tracks average fan-out, maximum non-entrypoint fan-out, entrypoint fan-out, maximum fan-in, large-coordinator count, large high-fan-out coordinator count, and worker-inclusive import cycles. The budgets live in config/architectureRules.cjs; if a future change makes a coordinator too broad or turns a shared module into a hub, CI fails with the exact module names.
tests/architecture/maintainabilityFitness.mjs adds layer-aware maintainability budgets using standard ESLint rule semantics for maximum module lines, function length, cyclomatic complexity, nesting depth, and parameter count. It also runs a local cognitive-complexity approximation over parsed JavaScript functions so deeply nested or hard-to-follow control flow is visible even when basic cyclomatic complexity still passes. These are intentionally measured by layer because a public component wrapper, a stateful feature coordinator, and a low-level library module do not carry the same risk profile.
tests/architecture/cohesionFitness.mjs checks folder-level separation of concerns. It measures external imports per module, incoming external imports per module, and internal import ratio for architectural folders such as src/features/table/virtual-table, src/ui/field-picker, and src/lib/workbook-export.
The same cohesion test also runs a Git-history change-coupling analysis. Bulk refactor commits are skipped, and high-confidence cross-folder file pairs are budgeted so files that repeatedly change together are surfaced as candidates for a clearer boundary. GitHub Actions fetches full history for the normal test workflow so this check has enough data in CI.
For a human-readable metrics report, run:
npm run architecture:metricsFor a single integer architecture score, run:
npm run architecture:scoreThe score is a 0-100 composite over the same executable guardrails: graph integrity, coupling and modularity, maintainability, folder cohesion, and Git-history change coupling. It is intentionally integer-based so progress can be tracked without subjective grading language. The CI gate enforces the minimum in config/architectureRules.cjs, while the target score marks the level the project should work toward for near-perfect modularity.
Run the full quality gate:
npm testThat runs:
npm run lint: syntax, globals, module rules, query-state boundaries.npm run test:architecture: architecture fitness checks, coupling/modularity budgets, folder cohesion budgets, Git-history change-coupling budgets, maintainability and cognitive-complexity budgets, legacy module budgets, forbidden browser globals, import graph reachability, import cycles, layer boundaries, canonical ES module specifiers, cache-stable import paths, and the hardcoded-field integration guard.npm run test:unit: focused pure-logic tests for query-history status, request mapping, row output, backend payload contracts, and table transforms.npm run test:browser: Playwright smoke test for runtime behavior and key UI styling.
All .mjs tests run through Node's built-in test runner with explicit test names. See tests/README.md for the test-layer standards and redundancy rules.
No application module currently uses a legacy large-module budget. The architecture fitness test enforces the normal module size limit across the app, so new large modules should be split instead of added to an exception list.
Future structure work should be cohesion-driven, not size-driven. Large feature shells can stay intact when they own a single workflow and delegate testable logic to focused modules. Small modules should only remain split when they isolate a stable contract, pure logic, worker entrypoint, or feature boundary.
- Audit the remaining large shells for mixed responsibilities before extracting anything else.
- Consolidate tiny single-use helpers when their boundary does not improve testing, ownership, or reuse.
- Keep runtime dependencies minimal; add production packages only when app modules import them directly and the dependency earns its weight.
- Continue expanding interaction tests around real workflows rather than adding thin tests around implementation details.