Architecture
Version 2 · Document the fourth mounted module and QWebHub-owned shared theme architecture.
QWebHub Architecture
QWebHub is a shared Django hub that mounts several otherwise-independent Django projects (QDBAdmin, QProcess, QDG KB Viewer) under one server, each under its own URL prefix, plus a landing page listing them.
The module registry (the core pattern)
Everything routes through one file: qwebhub/modules.py. A Module is a
frozen dataclass:
@dataclass(frozen=True)
class Module:
key: str
url_prefix: str
type: str # "django" | "asgi" | "proxy"
project_subdir: str = ""
urlconf: str = ""
app: str = ""
db_alias: str = "qdb"
asgi_factory: str = "" # future
proxy_target: str = "" # future
enabled: bool = True
notes: str = ""
MODULES is a plain list of these. Today it has 4 entries, all type="django":
qdbadmin (→ AdminManagement/QDBAdmin), qprocess (→ QProcess), qdgkb
(→ QDG KB Viewer, the read-only Knowledge Base viewer, db_alias="qdgwiki"), and garybreeding (→ Gary Breeding Report).
asgi/proxy types are already scaffolded in the dataclass for future
non-Django modules but nothing uses them yet.
Adding a new linked project is one Module(...) entry appended to this
list — nothing else needs to change. This was confirmed by direct code
reading (see ClickUp task 86d40jy36), tracing through every layer below.
How one registry entry becomes a working link
qwebhub/urls.pyloopsenabled_django_modules()and appendspath(m.url_prefix, include(m.urlconf))for each — the root URLconf is entirely generated from the registry, never hand-edited per module.core/views.py'sindexview reads the sameenabled_django_modules()list and passes{key, url_prefix, notes}per module to the template. A separatehealthzview returns{"status": "ok", "modules": [list of keys]}as JSON — a lightweight liveness/inventory check.core/templates/hub/index.htmlloops over that list and renders one card per module (key, notes, link to/{{ url_prefix }}) — nothing about any specific project is hardcoded in the template.
Each Module.project_root resolves project_subdir against PROJECTS_ROOT
(from QWEBHUB_PROJECTS_ROOT env var, or auto-detected as the folder
containing QWebHub itself) — so the same registry works unmodified on a dev
laptop and on the server, as long as sibling project folders exist at the
same relative layout.
Shared theme ownership
QWebHub is the controlling light/dark theme layer for every mounted Django
module. SharedThemeMiddleware injects the QWebHub-owned bootstrap script near
the start of each HTML <head> and the palette override at the end. Non-HTML,
streaming, and encoded responses are left unchanged.
The established qdb-theme selection is stored in browser local storage and a
path-wide cookie. The bootstrap sets data-theme and data-bs-theme before
module styling renders, so one selection follows the user between the hub,
QDBAdmin, QProcess, QDG KB Viewer, and Gary Breeding Reports. Modules should use
compatible CSS variables rather than create independent theme preferences.
QWebHub v1.2.2 defines light mode as a neutral light-grey background
(#eceff1) with near-white cards, white inputs, dark text, 500-weight body
copy, and 600-weight labels. Dark-mode tokens remain unchanged.
Constraint that must never be reintroduced: template/static namespacing
Django's template loader is one flat search list across every configured
directory. Each module's own top-level templates (e.g. base.html) must
live under a subfolder namespaced to that module (templates/core/base.html
for QDBAdmin, templates/racing/base.html for QProcess, core/templates/hub/base.html
for the hub itself) — never a bare templates/base.html. A shared,
un-namespaced file would silently shadow another module's layout with no
error, just a broken-looking page.
This was a real regression, fixed 2026-08-01 across all three modules, and
is documented as a hard rule in qwebhub/config.py's
module_template_dirs(). The same namespacing applies to static assets
(module_static_dirs()) — modules namespace assets below static/<module-key>/
for the same collision-safety reason.
Related pages
- Database Configuration — how
db.jsonresolution and theqdb/qdgwikialiases work. - Odin Deployment — how changes actually reach the server.
- QWebHub Q&A — credential rotation procedures.