Two new docs to round out the lnbits-specific reference set:
docs/lnbits-frontend-gotchas.md — the Vue/Quasar UMD traps that don't
manifest under the build-step model most tutorials assume. Sections:
- No self-closing tags. UMD-loaded templates are parsed by the
browser's HTML parser, which doesn't honor self-close on non-void
elements; nesting silently breaks. Fine in .vue SFCs because the
build step rewrites them — but lnbits templates aren't SFCs.
- CSS specificity vs Quasar utilities. LNbits applies !important
overrides on .text-caption / .text-grey-*; class-based rules in
extension pages lose. Reach for inline :style bindings or static
style="" attrs instead.
- Cache busting via ?v={server_startup_time}. Bumping JS requires a
server restart; templates re-render every request.
- Dark-mode color discipline. bg-{color}-1 utilities don't get a
default text-inversion in dark theme; pair every pale background
with an explicit text class.
docs/lnbits-extension-dev.md — auth + testing + migration pattern.
Sections:
- Auth decorators table: require_invoice_key, require_admin_key,
check_admin, check_super_user. The require_admin_key vs
check_admin distinction is the most common misuse — one is
wallet-level write access (any user), the other is instance admin.
- Testing: FakeWallet for CRUD/API/UI, regtest only for real
Lightning behavior. Why the dev CLI defaults to --fakewallet.
- The migrations_fork.py pattern: keep migrations.py byte-identical
to upstream, put fork-only schema deltas in a sibling file loaded
under <ext>_fork in dbversions. Covers: architecture facts
(dbversions in core DB, no cross-DB atomicity → idempotent
migrations mandatory), squash recipe for adoption, one-time
dbversions surgery for installs that previously ran old fork
migrations, the upstream-overlap rebase scenario.
Both linked from README "Further reading". No personal identity in
either file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.7 KiB
lnbits frontend gotchas
LNbits ships its UI with Vue 3 + Quasar 2 as UMD globals — no
build step, plain Jinja templates with per-page JS. This applies
across lnbits core (lnbits/templates/*.html), every extension
(<ext>/templates/), and every fork that doesn't restructure the
frontend stack. The UMD load model has several traps that don't
manifest under the build-step model most Vue tutorials assume.
No self-closing tags
Per Quasar's UMD usage rules, components must use the explicit-close form:
<!-- correct -->
<q-input v-model="foo" label="Foo"></q-input>
<q-btn @click="bar" label="Bar"></q-btn>
<!-- wrong — silently broken in UMD/no-build mode -->
<q-input v-model="foo" label="Foo" />
<q-btn @click="bar" label="Bar" />
Why: UMD-loaded templates are parsed by the browser's HTML parser, not Vue's compiler. The HTML parser doesn't honor self-close on non-void elements (per the HTML spec). The close tag gets implied at the wrong place, nesting breaks silently, and subsequent siblings end up nested inside the prior component.
Self-closing is fine in .vue SFCs (the build step rewrites them
before the browser sees anything), so if you copy a snippet from a
Vue SFC repo into an LNbits template, expand all self-closing tags
before saving.
CSS specificity trap
LNbits applies its own theme overrides on Quasar's typography
utilities (.text-caption, .text-grey-*, etc.) with !important.
Class-based CSS rules in an extension page — even with !important —
lose this fight unless your selector is strictly more specific than
the upstream rule.
Rule: for per-element typography/color overrides on LNbits pages,
reach for Vue :style bindings (or static style="..." attrs), not
<style> blocks targeting Quasar utility classes:
<!-- ✗ likely loses to upstream's !important rule -->
<style>
.text-caption.my-fix { color: #ff0000 !important; }
</style>
<!-- ✓ inline style wins without an arms race -->
<span :style="{ color: '#ff0000' }">…</span>
<span style="color: #ff0000">…</span>
Background/border tweaks at card-level via class are fine — the trap
is specifically the typography utilities (text-*) and Quasar's
color utilities.
Cache busting
Static assets are served with ?v={server_startup_time} appended
(see static_url_for in lnbits/helpers.py). Consequences:
- Bumping JS requires a server restart. Reloading the browser
doesn't help if
?v=hasn't changed — the browser keeps serving the cached file. - Jinja templates re-render on every request (the
?v=is only on static assets). No restart needed for template edits — just refresh.
If a browser keeps serving stale JS after a restart, hard-refresh
(Ctrl+Shift+R) to bypass the HTTP cache.
Dark-mode color discipline
LNbits's dark theme inverts text colors on most surfaces but not
on bg-{color}-1 pale-background utilities. Result: a bg-red-1
without an explicit text color renders white-on-cream under dark
theme — basically invisible.
Rule: pair every pale-background utility with an explicit dark text class:
<!-- ✗ unreadable on dark theme -->
<div class="bg-red-1 q-pa-md">Warning</div>
<!-- ✓ explicit text class survives theme switch -->
<div class="bg-red-1 text-grey-9 q-pa-md">Warning</div>
Same for bg-green-1, bg-blue-1, bg-amber-1, etc. The text-grey-9
choice is the safe default; pick a darker shade if you want stronger
contrast.
When in doubt
Test under both light and dark themes (Quasar's theme toggle is at the top of every LNbits page once you're logged in). Most of the above gotchas are silent under one theme and obvious under the other — don't ship UI without flipping the toggle at least once.