Skip to content

Quantum 1.0.0 ​

Quantum 1.0: declarative web applications in XML, with AI and RAG built into the language. No build chain, no JavaScript, no front-end framework.

The road from the first public release (0.9.0) to here was mostly about making that sentence true, and only that sentence:

  • What is promised is written down. SUPPORT_TIERS.md splits the language into Core (the web framework: components, state, conditions, loops, functions, parameterised queries and transactions, actions and forms, composition, files, mail, the Core set of ui:*, authentication), AI (q:llm, q:knowledge, q:agent), Experimental and Laboratory, and the engine enforces it: a tag outside Core and AI warns once when it runs (0.10.0, 0.21.0).
  • What a program means is specified. SPEC.md states the Core and AI as rules with IDs, frozen at 1.0, and every rule is cited by a conformance test that fails if the rule and the runtime disagree (tests/conformance/, with test_spec_ids.py checking both directions). Decisions that used to be silent — a missing name, a failed expression, a missing key, an attribute that did nothing — are errors that say where they are (0.11.0 through 1.0).
  • What is claimed is run. Apps in the repository prove the tiers: projects/tarefas (the UI engine), projects/blog and projects/helpdesk (Core, files and mail), projects/bank-transfer (q:transaction), projects/docs-assistant and projects/shop-agent (AI). quantum test (0.22.0) tests an app in its own language, on a fresh database built from its migrations; the apps' suites run in CI. FEATURE_STATUS.md is generated by executing every example, never written by hand.
  • AI you can build on. Answers cite the chunks they came from and say when they are grounded, a relevance floor keeps the model from answering from nothing, answers can stream, and every AI tag has a failure contract (onerror, time budgets) instead of an exception in the page (0.20.0, 0.22.0).
  • One page, three renderers. The same .q page with ui:* is drawn by the browser (quantum start), the terminal (quantum console) and a desktop window (quantum desktop), with forms that know their action's rules, tables from data and a declared responsive layout (0.15.0 through 0.18.0).
  • Tools around the language. quantum check (pages, SQL and query fields against the database), migrate plan from a schema.sql, change history, the /_dev panel, errors that point to the line (0.17.0, 0.19.0).
  • In English. Code, messages, tests, the SPEC and the documentation.

The changes since 0.22.0 — released as part of 1.0, with no 0.23 of their own — are below.

1.0: changes since 0.22.0 ​

Added ​

  • get(x, key, default) in expressions reads a key that may be missing, or a list index that may be out of range, and gives default (null when left out) (EXPR-16).

  • quantum start --hot-reload (and --hot-reload-port): the open pages reload when a component or static file is saved, CSS changes restyle without reloading, and a .q that no longer parses shows its error on the page (DEV-4). The watcher, the WebSocket and the page script existed, but no command started them.

  • random(), random(a, b), chance(p) and pick(list) in expressions (EXPR-15).

  • The HTML a .q accepts is a rule (PARSE-4): boolean attributes, a bare &, void elements left open, < inside a quoted attribute value and named HTML entities.

  • CI checks the game projects (snake, tictactoe, kenney-platformer) build to valid JavaScript, serve their game and are committed up to date (Laboratory job).

Breaking ​

  • Laboratory (2D game engine and Godot codegen): the game names are neutral. A game now writes death-sequence="classic", calls game.setPatrolAI, and uses the Godot codegen's tags item_block and bonus_coin, its HUD counter bonus_coins, its sound trigger player-died and its event item-block-hit; the world map moves the sprite id="player". The 0.22 names for these are no longer accepted. Behaviour is otherwise the same; the games in the repository were migrated and rebuilt.
  • Admin: GET /datasources/{id}/logs answers failures with an HTTP status and a detail instead of 200 and an error key: 404 for an unknown datasource or a container that no longer exists, 409 for a datasource with no container, 503 when Docker is not available or does not answer. ?tail= is unchanged; the datasource logs dialog reads the status.
  • d['missing'] on an object without that key is an error that names the key and suggests a similar one, as d.missing already was (EXPR-16, was G19); it used to be null, so a misspelt key rendered nothing. Read an optional key with get(d, 'key', default) or test it with 'key' in d.
  • skip_rows on q:data skips the first lines of the file, before the header, as the name says; it used to read the first line as the header and then drop data rows (DATA-1).
  • Attributes that were accepted and did nothing are parse errors that say so (PARSE-3): model on q:knowledge (the model is chosen on q:llm, IA-2), unique on q:set (use operation="unique"), every attribute of q:column but name and type (required, default, validate, pattern, min, max, minlength, maxlength, range, enum; filter rows with q:transform), and q:flash outside a q:action (ACT-3).
  • in compares like == (EXPR-14): '5' in [5] and 5 in ['5'] are true — a value from a form now finds its number in a list. in on something that is not a list, an object or a text is an error.
  • q:set without type keeps the type of a value that is exactly one expression, like q:return and props (SET-5): value="{[1, 2]}" stores the list, value="{len(x)}" the number, value="{dateAdd('h', 8)}" the date. It used to store text, so len() counted characters and {n + 1} on a stored number failed. Text with several parts ("{n} items") and literals ("007") are still text. To keep the old behaviour, write type="string" — the blog's and the admin's login pages now do, for the session's expiry.
  • A value outside an attribute's list is a parse error with the line (PARSE-5): q:set type, operation, scope and validate (date, datetime, struct and null were accepted as q:set types and did nothing); q:loop type; q:param type (inside q:query only the types the query understands — number there is now reported where it is written, it failed when the query ran); q:invoke method and authType. An unknown authType used to send the request with no credentials; an unknown form-field type was plain text.
  • q:loop items= over something that is not a list is an error (LOOP-6), in a statement and in markup, like a ui:table source: items="{5}" looped once over "5", and in markup a missing value drew zero rows. Give a value that may not exist yet a list first: <q:set name="session.cart" type="array" value="{session.cart}" default="[]"/>.
  • q:invoke responseFormat="json" on a response that is not JSON is a failure (INV-2), with the Content-Type and the start of the body; it used to succeed with {"error", "text"} as the value.

Removed ​

  • quantum deploy and quantum apps: they talked to a deploy service that was never published, so for anyone else they could only fail. Serve an app with quantum start behind your own server (DEPLOYMENT.md).
  • The examples built on third-party game art that cannot be redistributed; the games that remain use the Kenney assets (CC0).
  • The Codecov upload from CI: it needed a repository token and was failing (429) without one; the coverage floor is enforced in CI by coverage report --fail-under.
  • The admin's deploy feature, which lived only in the legacy FastAPI backend (quantum_admin/backend; the .q admin that ships never had it) and deployed nothing real: DeployService, the deploy pipeline with versions and rollback, the per-project CI/CD retry, auto-deploy on a webhook push, the cloud integrations (AWS, Kubernetes, Azure, GCP), the deploy-only environment fields and the deploy screens. The generic Docker features for datasources stay; webhooks still record events; a global.yaml with a deployment: block and an admin database with deploy columns still load.
  • projects/quantum-landing: a marketing page that promised what does not ship (version 1.0.0, one-command deploy, the game engine as a feature) and linked to demos that no longer exist.
  • projects/llm-demo: an LLM chat superseded by projects/docs-assistant; its script inserted the server's reply into the page with innerHTML (XSS).
  • projects/quantum-rag: a RAG demo over a stale copy of the guide, superseded by projects/docs-assistant.
  • projects/quantum-fighter: only a prebuilt game.html, with no .q source to build it from.
  • projects/quantum-tower and its copy examples/tower_defense.q: the game called runtime functions that do not exist (scheduleOnce, createSprite, attachBehavior, removeClickable, clickedSprite), so no wave came and no tower could be built, and eight of its images and sounds were never committed.

Fixed ​

  • The admin's pinned stack is current: fastapi 0.109 (January 2024) -> 0.141, with starlette 1.x, pydantic 2.13, pydantic-settings 2.15, uvicorn 0.53, httpx 0.28 and websockets 16 (uvicorn's standard extra needs >= 13). CI had been testing a different admin than the one developers run. Starlette 1.x's TestClient uses httpx2 (pinned alongside; the admin itself still calls httpx), and the pytest filter for the old Starlette's anyio deprecation is gone: 0 warnings without it.
  • quantum console: after a pause in a search-as-you-type field, the redrawn field got the focus with its text selected, so the next key replaced what had been typed ("b", pause, "a" searched "a"). It keeps the text, with the cursor at the end (UI-12). The console tests wait for the search of what was typed, not for a page load; the browser test waits for the URL of the whole text.
  • The editor schemas offer the values the parser accepts: q:param type listed datetime for every param (only a q:query's takes it) and lacked time, q:invoke method lacked HEAD/OPTIONS, and validate did not say a regular expression starting with ^ is accepted (it was an enum, so the language server flagged one). test_editor_schemas.py compares every closed list the parser checks with the schema.
  • In HTML content, an expression that uses a scope variable inside something larger — {session.visits + 1}, {session.n > 0}, {session.user.name} — rendered empty although the variable existed (it was read as the session key "visits + 1"); cookie.x could not be read inside an expression at all. It is read as in q:set, everywhere (EXPR-11).
  • q:set type= int, long, numeric, float, double and text — the names q:param accepts — convert like integer, number, decimal and string; they were accepted and converted nothing (type="int" stored the text "7") (ERR-1).
  • The test suite no longer writes into the repository. It left test_data/quantum_test.db, quantum_jobs.db, logs/, .quantum/knowledge and page bundles in static/ in the checkout; each writer now uses a temporary folder, and the root conftest.py fails the run, naming the files, when a test writes into the repository (git status, ignored files included, before and after).
  • The job threads of q:job, q:schedule and q:thread are the process's and stop when asked. The server builds a service container per request, and each built its own job executor: every request to a page with q:job started one more queue worker, and every request with q:schedule one more scheduler running the same schedule again, none of them ever stopped. The executor is now shared per job database; its workers wait on a stop signal instead of sleeping, stop_workers()/shutdown() join them (a job in progress finishes first), and they are shut down when the process exits.
  • components/bank_transfer_demo.q never ran (it queried a default datasource with tables nobody created, and read flash before it existed) and moved money wrongly: an overdraft or a transfer to an account that does not exist debited one side and credited nobody. It is now projects/bank-transfer, the proof app for q:transaction (DB-4): a SQLite config, a migration with three accounts and a CHECK (balance >= 0), both accounts checked before the transaction, and a quantum test suite in CI.
  • tests/apps/test_ui_parity_script.py failed now and then under -n auto: a pilot's pause could return while a button's Pressed message was still bubbling to the app, so the test read the page before the click's page load had started (measured: 1 click in 40). quantum console counts finished page loads (pages_loaded), and the console tests wait for the load their own action caused (tests/console_pilot.py) — no sleeps, no retries.
  • The editor tooling describes the language the parser accepts: quantum-lsp's Core and AI tags have exactly the attributes the parser reads and does not refuse (vscode-quantum's q:llm had no knowledge/top/minRelevance; the agent, tool and data sub-tags were missing), and both tools stop offering q:persist, q:route, q:component basePath/health/metrics/trace and the other removed attributes. vscode-quantum's Core/AI schema and grammar are generated from quantum-lsp (scripts/generate-editor-schemas.py), its snippets all parse, and tests/conformance/test_editor_schemas.py fails when either drifts from the parser.
  • model= on q:llm, q:agent and q:team agents takes expressions, like endpoint= and apiKey=; model="{m}" was sent to the server literally (IA-1).
  • range= on a q:action param is checked, as on a q:function param (ACT-2, FN-1).
  • encoding= on q:data reads the file (or response) in that encoding; every file was read as UTF-8 (DATA-1).
  • A tool argument the model leaves out takes its q:param's default (IA-4).
  • The PARSE-3 test checks that a field is read by the code that runs its node, not only that its name appears somewhere in the runtime.
  • A date in the session came back shifted by the server's UTC offset (Flask read a date without a timezone as UTC): it comes back the same date, and sessionExpiry may be a date or ISO text (SET-5).
  • The redis message broker (MESSAGE_BROKER_TYPE=redis, quantum mq) could not load: its module imported message_broker by a bare name, so asking for it said "requires 'redis' package" even with redis installed.
  • A q:action read and wrote an empty application scope of its own: application.x set in an action was lost and could not be read there; actions share the server's application scope, as pages do (EXEC-3).
  • projects/quantum-chat works again (every post was a 500): its forms are q:actions, its dead runtime patches and a committed session key are gone, and a quantum test suite runs in CI.
  • projects/quantum-terminal's download links answered 404 under quantum start.
  • The error for <q:application type="testing"> and qtest: tags names quantum test as available; it said "coming in a later release" (APP-2).
  • quantum start with hot reload: stopping the reload server no longer leaves its event loop and sockets behind ("RuntimeError: Event loop is closed" printed later); it uses the current websockets API.
  • Reconfiguring logging closes the previous log files instead of leaking them; the websocket transport closes its event loop on shutdown.
  • Admin: no deprecation warnings (datetime.utcnow, Pydantic class Config, FastAPI on_event); an unreachable duplicate GET /datasources/{id}/logs route was removed; the deploy health check and the project server start/stop no longer leak a connection or a child process.
  • services: modules are no longer re-executed when the registry is reloaded; their classes stay the same objects.
  • Dates bound to SQLite use explicit adapters (the same ISO text), instead of the default ones Python 3.12 deprecated.
  • The test suite runs with no warnings and no flaky failures (17 runs in a row, shuffled): a test left pytest's own PID where quantum stop would kill it.
  • A persisted q:knowledge base is stored in the working directory it is indexed from (IA-2). The default ./.quantum/knowledge was handed to ChromaDB as a relative path, which it keys one store per process by, so a process that indexed bases from two directories wrote both to the first (and failed with "Failed to get segments" once it was gone — a flaky CI test). In one component, a persist="false" base no longer made the bases after it in-memory too.
  • quantum stop no longer kills a process it did not start (RUN-3): a stale .quantum.pid whose PID the system had given to another program made it kill that program. The file records each process's start time, and a PID whose process started at another time is left alone (the stale file is removed, exit 1). A .quantum.pid written by 0.22 or earlier has no start time: stop refuses it once, and the server is stopped by hand.

Documented ​

  • The docs build fails on a link to a page that does not exist (ignoreDeadLinks: false), and CI builds the docs on every push and pull request, not only when publishing them.
  • The Hot Reload page described quantum dev, configuration keys, environment variables and partial updates that never existed; it now describes quantum start --hot-reload as it works.
  • SPEC gains LOOP-5 (loop types), SET-3 (q:set operation and scope) and SET-4 (q:set validation), and extends DB-1, ACT-3 (q:flash), DATA-1, INV-1, FILE-1, MAIL-1 and IA-1/2/4 — behaviour that already existed; every rule is exercised by a test that cites it.
  • Every Core and AI tag has a checked example in the guide; the guide explains using one component inside another (q:import, props, q:slot).

← 0.22.0 · All versions

MIT Licensed · Built with VitePress