`test_coverage` sets the floor the Lens pass enforces, measured first: 74.53%
with test/support excluded (`mix test --cover` and the exported sample give the
same number). The rule — a FLOOR, ratcheted up as behavior yaks land, never
lowered — is written next to the config, along with why each module is excluded
(test scaffolding only: fixtures measure the tests, not the library).
Wiring, per the fast/push split: `test.coverage` runs the suite once with
`--cover --export-coverage default`; the `coverage` sensor then enforces the
floor from that export via `mix test.coverage` (no second suite run). So
`verify` runs the suite first and the sensors after it. The sensor guards on the
export existing: `mix test.coverage` exits 0 when it finds no .coverdata, and a
floor check that did not run must not report success.
blogex/.gitignore: cover/ (generated).
`redact: true` makes Ecto derive an Inspect impl at the `schema` line.
Elixir 1.20.4 emits that whole derived impl on that one line, and OTP 27's
coverage instrumentation (sys_coverage) turns it into invalid Erlang
("variable '_2' unsafe in 'case'"), so `mix test --cover` aborted for the whole
app before reporting anything. Minimal repro, no Ecto needed:
`@derive Inspect` on a plain struct fails on Elixir 1.20.4 + OTP 27.3.4.19 and
works on Elixir 1.18.3 + the same OTP.
Writing the impl out by hand, one expression per line, keeps `inspect/1`
output byte-identical (Ecto/Elixir render redacted fields as
`#Mod<..., ...>`) and keeps redaction pinned by the existing test, and lets the
coverage sensor land. Revisit when the Elixir/OTP pins change.
Records the two loops (make check per commit, make verify per push), how to
install the hook (make hooks), the deliberate --no-verify bypass, the Lens
practice (separate pass, don't chase nits inline, file a yak), the triage
classes (real bug / environment-dependent test / design nit), and how that
reconciles with the zero-defects policy. Playwright stays outside the Lens
pass pending an explicit decision.
.githooks/pre-push runs 'make verify', tees the log to .git/lens-pass.log and,
on failure, prints one summary (last step started, log path, bypass) before
exiting 1. 'make hooks' symlinks .githooks/* into the clone's hooks dir.
core.hooksPath is deliberately left alone: bd keeps its own pre-commit
(post-merge) hooks in .git/hooks and pointing hooksPath at .githooks would
silently retire them while bd still reports them installed.
Root verify runs check first, then a per-app verify in each Makefile: sensor
slots hooked to VERIFY_SENSORS (empty today, so nothing is claimed that is not
run) and then that app's suite. Measured: 9.9s wall, app 178 + blogex 156 tests.
Root check now loops app then blogex then the pin check, mirroring the shape
the Lens pass will use. blogex gains a check target (compile with warnings
fatal + format); credo is a marked slot until its dep exists, and the gate
never claims a sensor it did not run.
Records the plan for bringing coverage floors, CRAP scores, credo strict,
sobelow, boundary and mutation testing to firehose, framed as the
"internal verification" counterpart to the existing Playwright / conn /
LiveView tests.
Ordered in phases because the fast/push split (make verify + pre-push
hook, cheap precommit) is the container the other sensors wire into.
Includes the steering principles that made the stickies run work
(behavior not state, functional core/imperative shell, delete dead code,
leave defensive branches uncovered deliberately) and the deferred
decisions (CRAP threshold below 30, boundary.spec.check, Playwright in
the Lens pass).
The fire-and-forget run captured its session's pi/ctx; after /new, fork,
switch, or reload pi invalidates those, so the orphaned run's notify/
sendMessage threw 'This extension ctx is stale after session replacement
or reload'. Now session_shutdown aborts the in-flight run before
invalidation (pi.exec supports AbortSignal), and stale errors are
swallowed as a race guard.
Also show a 'running tests...' footer status widget while the run is
in flight, cleared on completion.
Emit test-balls:record on the shared pi.events bus after the session-start
make test run completes, so test-balls shows a ball for it like any other run.
The run is now fire-and-forget: session start no longer blocks on make test,
and the emit always lands after every extension's session_start handler has
run (project extensions load before global ones, so emitting synchronously
raced test-balls' runs reset). Exactly one ball is recorded per run, even if
UI reporting fails.
mise now lives at /opt/homebrew/bin/mise (Homebrew install), so the
hardcoded MISE_BIN default broke make test/deps/compile. Fall back to
PATH lookup, matching what the check target already did.
The microprints git dep now allows phoenix_live_view ~> 1.2, so the
app can take the CVE-2026-64941 fix (open redirect in
validate_local_url!/2, fixed in 1.2.9). No 1.1.x backport existed.
blogex test lock follows to 1.2.12 so the library is tested against
the version the app ships with. mix deps.get now reports zero
security advisories.
- Restore HTML link extraction in LinkValidator (removed in a83634d
under the false premise that post bodies are raw markdown; they
are HTML rendered by NimblePublisher at compile time). The missing
regex made extract_links/1 find zero links, silently disabling
compile-time validation.
- Support /blog/{blog_id}/tag/{tag} links: validate blog ID,
require non-empty tag (tags are user-defined, e.g. pi.dev).
- Fix invalid links in two posts: tag/Pi.dev -> tag/pi.dev,
2026-07-13-synthetic-tdd.md -> synthetic-tdd.
- Fix test warnings: use Plug.Test deprecation, unused import,
runtime-defined TestBlogValid module.
- Add regression tests for HTML extraction and tag page links.
External links in post bodies now open in a new tab and show a
box-with-arrow marker icon, so readers can tell at a glance which
links leave the site.
- blogex: new Blogex.ExternalLinks.rewrite/1 marks up anchors with
http(s) hrefs at compile time with target="_blank", rel="noopener"
and class="blogex-external" (idempotent, merges into existing
class/rel values, leaves relative, mailto: and #anchor links alone)
- blogex: Post.build/3 applies the rewrite, so every blog gets it
- app: CSS in .blogex-post-body draws the marker via a mask so it
inherits the link colour; empty ::after content stays invisible
to screen readers
- make test now also runs the blogex suite; it is currently red
with 3 pre-existing blog_integration_test failures caused by the
vacuous link validator (parked yak
link-validator-never-sees-compiled-html-...)