Contributing to AiOverviewControl
August 26, 2026 · View on GitHub
Thanks for helping improve the plugin. This guide covers the local dev loop, the CI gates you must satisfy, and the QML/shell gotchas that have bitten us before.
Project layout
| Path | Purpose |
|---|---|
AiOverviewControlWidget.qml | Main UI — dankbar pill + popout dashboard. |
AiOverviewControlSettings.qml | Plugin settings page. |
AiOverviewControlI18n.qml | Locale loading + string interpolation (singleton). |
ProviderLogo.qml | Local provider-logo resolution, tinting, and fallback icons. |
i18n/*.json | Translation bundles (en is the source of truth). |
providers/get-* | Provider adapters and helpers; get-provider-usage dispatches. |
scripts/package-release | Builds and validates the installable release archives. |
Dev loop
Hot-reload the running plugin without restarting the shell:
qs -p ~/.config/quickshell/dms ipc call plugins reload aiOverviewControl
# → PLUGIN_RELOAD_SUCCESS: aiOverviewControl
Two caveats that cost real debugging time:
- Reopen the popout after every reload. A reload does not re-render a popout that is already open — the old instance stays in memory. Close and reopen it to see UI changes.
AiOverviewControlI18nis apragma Singletonand is frozen at the quickshell process level. A plugin reload re-instantiates the widget but not the singleton: both its cached translation bundles and its code stay as they were when the shell process started. New i18n keys (or singleton code changes) only appear after a fullqsrestart. The widget callsAiOverviewControlI18n.refresh()on completion (typeof-guarded) so future sessions re-read the JSON on reload, but the session that introduced the change still needs a restart.
QML gotchas
-
modelDatain customRepeaterdelegates. ARepeaterinjectsmodelDatainto a plain inline delegate, but a delegate that is a custom component (e.g.UsageBar { ... }) does not receive it implicitly — you must declarerequired property var modelDatainside the delegate, or every binding that readsmodelDatasilently renders blank. This is exactly what broke the v1.4.5 hero window bars. When a delegate is a custom component and readsmodelData, add the required property. -
Contained rounded indicators, not full-height stripes. A left accent bar anchored top-to-bottom inside a
radius/clip: trueparent bleeds square corners past the rounded edge. Use the contained treatment instead:anchors.verticalCenter, a height inset from the parent, andradius: width / 2(see the provider card andMetricTileindicators). -
Local
qmllintimport noise. With the Qt5qmllint(default, no-U) the files lint clean. If you pass-Uor use the Qt6qmllint, you will see unavoidableqs.*import-resolution warnings for DankMaterialShell modules that are not on the lint path — filter them:qmllint *.qml 2>&1 | grep -vE "qs\.|was not found|Unqualified|import"
Providers
- Most API-backed and informational
providers/get-<id>-usagestubsexecget-provider-wrapper <id>, which calls intoget-provider-usage'sfetch_provider()dispatch. A case iscanonical|alias1|alias2); the first token is the canonical provider that owns a stub, and the rest are documented aliases. Codex, Claude, and Copilot use specialized helpers;piuses an inline dispatcher envelope plusget-pi-analyticsrather than a generic usage stub. - Scripts must be executable (
chmod +x) and passshellcheck.
CI gates (run these locally before pushing)
| Gate | Local command |
|---|---|
plugin.json valid + semver | jq -e . plugin.json |
| i18n JSON valid | for f in i18n/*.json; do jq -e . "$f"; done |
i18n key parity (all locales == en) | `for locale in pt_BR zh_CN es_ES de_DE; do diff <(jq -r 'keys[]' i18n/en.json) <(jq -r 'keys[]' "i18n/$locale.json") |
CHANGELOG has the plugin.json version | VERSION="$(jq -r .version plugin.json)"; grep -qF "## $VERSION" CHANGELOG.md || grep -qF "## [$VERSION]" CHANGELOG.md |
| QML lint (hard gate) | qmllint AiOverviewControlWidget.qml AiOverviewControlSettings.qml AiOverviewControlI18n.qml ProviderLogo.qml |
| Shell syntax | find providers -maxdepth 1 -type f -print0 | xargs -0 bash -n; for test in tests/*.sh; do bash -n "$test"; done; bash -n scripts/package-release |
| Shell lint | shellcheck -S warning providers/* tests/*.sh scripts/package-release |
| Release package | scripts/package-release |
Parity is strict: every key in en.json must exist in pt_BR, zh_CN,
es_ES, and de_DE, and vice versa. Prefer t("key", "English fallback")
calls so a missing translation degrades gracefully, but the key must still be
present in all five bundles.
Release
See docs/release-checklist.md. In short: bump
plugin.json (patch for fixes/UI, minor for features), add a CHANGELOG.md
entry, commit, push main, wait for green CI, then tag vX.Y.Z (the tag
must equal the plugin.json version — release.yml validates it) and push the
tag to trigger the release build.