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>
105 lines
3.7 KiB
Markdown
105 lines
3.7 KiB
Markdown
# 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](https://quasar.dev/start/umd/#usage),
|
|
components must use the explicit-close form:
|
|
|
|
```html
|
|
<!-- 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:
|
|
|
|
```html
|
|
<!-- ✗ 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:
|
|
|
|
```html
|
|
<!-- ✗ 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.
|