Perch
Building modsDebugging

Debugging

The dev tap, the log taps, the display dump, and the complete failure table.

2 min readUpdated Jul 21, 2026

The dev loop

PERCH_MOD_DEV=~/perch-dev perch

The dev tap loads every package folder under the given directory alongside the builtins. Edit, restart Perch, observe — a package that fails to load shows its exact error in Settings → Mods and touches nothing else.

Taps

TapWhat it gives you
PERCH_LOG=mods=debugthe parsed manifest at DEBUG, every load/validation error with its node path, open/command activity traced
PERCH_DUMP_DISPLAYS=<path>writes the computed display trees to disk for inspection
host.log(msg)your own prints — prefixed perch: mod[<id>]: in the app log
PERCH_MOD_DEV=~/perch-dev PERCH_LOG=mods=debug perch

Reading a validation error

mod[worldclock] displays.card.children[1].children[2]:
  component 'text' has no prop 'colour'

The path is exact — count into your tree and you’re at the node. The validator checks components, props, value ranges, and the limits at load, so a tree that loads cannot fail at render.

The failure table

Every failure mode, one honest behavior each:

FailureBehavior
syntax error in the entrymod skipped; Settings shows the Lua error
api newer than this Perchskipped: “needs a newer perch”
unknown component / prop / over-limit treerejected at validation with the exact path
callback exceeds the instruction or 50 ms budgetaborted; strike 1 of 3; three consecutive = disabled with a notice; any clean callback resets
runaway push_datamelted at ~5/s; parked pushes merge per key, newest wins — never silently dropped
http outside granted scope / pinned hostrefused; callback gets (-1, "scope"); logged
touching Perch’s own pref dirdenied regardless of scope
broken theme valuestheme rejected at load; the picker never shows it
crash mid-callbackyour lua_State alone dies; your widgets retract cleanly; neighbours and the overlay never notice

Budget strikes in practice

If your mod dies with a budget notice, the usual suspects in order:

  1. Formatting in a hot event. on_media fires on every position poll — precompute what you can, and only push_data fields that changed.
  2. Rebuilding big tables per tick. Hoist constants out of handlers; your file-level locals persist for the mod’s life.
  3. Hidden O(n²). String concatenation in loops over list data — use table.concat.

The 50 ms wall and ~2M instruction budgets are generous for view-ready computation — a mod that hits them honestly is almost always doing work that belongs in a ticker, a draw painter, or C++‘s side of an async API.

esc
Type to search
navigate open