v0.35.2 — Capability manifest + marketplace polish
First major release since v0.34.1. Headline is the widget capability
manifest that closes the marketplace phase 2 work
(#2) — widgets now
declare which network hosts, settings sections, and filesystem paths
they need, and the host enforces network egress at the socket layer.
Plus a topbar battery indicator + a clutch of marketplace card polish.
Highlights
-
Widget capability manifest + enforcement. A widget can declare
what it needs in arequires:block inplugin.json:The host installs a hook over
socket.create_connection+
socket.socket.connectand gates every outbound connect against
the active widget's allowlist. A widget trying to phone home to
an undeclared host raises `CapabilityDenied` and the composer
surfaces it as a cell-level error rather than a render failure.
Settings + filesystem entries are review-only in v1 (Python's
open()andcurrent_app.configare reached through too many
paths to interpose cleanly without process isolation, which is
#3). Widgets
without arequires:block load with no enforcement (the
pre-#2 behaviour) so existing bundled + catalog widgets keep
working. New catalog submissions are expected to declare. -
Topbar battery indicator. When any registered device instance
reports abattery_pctheartbeat, a small Phosphor battery glyph
appears next to the theme toggle. Single-device installs show the
percentage inline; multi-device installs show a count badge with
a hover/click popover listing every battery + level. The trigger
paints in the tone of the worst battery so a single critical
device catches the eye when others are fine. -
Low-battery push overlay cleanup. Dropped the black border
(was picking up dither artifacts on some panel gamuts) and
re-anchored the Phosphor glyph to share a baseline with the
percentage text — they now read as on the same line instead of
the icon visibly floating above the digits. -
Marketplace card thumbnails fill the preview cleanly. After
two iterations on aspect-ratio + object-fit, settled on
object-fit: fillso the image always fills the card regardless
of source shape. Cards are thumbnails; the contributor's source
repo link is one click away if a user wants the true ratio. -
Catalog dogfooded with
requires:. All three live catalog
entries (community_demo, devref bundle, public_holiday_countdown)
bumped to declare their capability surface — the catalog no
longer relies on the legacy "unenforced" path, even though the
host loader keeps it for in-tree compat.
Under the hood
app/capabilities.py(new):Capabilitiesdataclass,
parse(),capability_scope()context manager, idempotent
install()/uninstall()socket hooks. ContextVar-backed so
concurrent renders don't cross-contaminate. The hook fires before
DNS so hard-coded IPs can't dodge the gate.app/plugin_loader.pyparsesrequires:during discovery
and hangs the snapshot onPlugin.capabilities. Malformed
entries log + drop rather than failing the load.app/composer.py_fetch_plugin_dataenters
capability_scope(plugin.capabilities)around every widget
fetch()call. `CapabilityDenied` surfaces as
{"error": "Capability denied: ..."}in the cell.app/app_factory.pyinstalls the hooks once after the
registry is built. Idempotent under the dev reloader.- Docs: new "Capabilities,
requires:" section in
docs/widgets.md
with the vocabulary table, enforcement sketch, backward compat,
and an honest threat-model section (what this catches:
manifest-vs-behaviour drift; what it doesn't catch: a determined
attacker with ctypes — real isolation is #3). Reviewer checklist
in docs/dev/publishing-a-widget.md
expanded to demandrequires:declarations on new submissions. - 3 bundled widgets ported as worked examples:
weather_nowclock_sunrise_sunsetdeclarenetwork:api.open-meteo.com;
clock_analogdeclaresrequires: [](the explicit "no
capabilities" form).
- 19 new tests covering parse / scope nesting / urllib
enforcement / legacy bypass / install idempotence. 853 total
pass; mypy --strict + ruff + mkdocs --strict all clean. - Catalog refreshed: all three live entries got fresh
1200×800 tight screenshots so the marketplace thumbs render
cleanly under the new fill behaviour.
See CHANGELOG.md
for the full breakdown.
{ "requires": [ "network:api.open-meteo.com", "settings:app" ] }