Commit Graph
329 Commits
Author SHA1 Message Date
Firehose Bot 1709cb78a1 intention-location details pattern 2026-10-29 16:51:38 +00:00
Willem van den Ende f5faa8a3af yak(theme-explicit-light-is-overridden-on-dark-os-browsers-chip-backgrounds-are-dropped-pku7): theme: explicit light is overridden on dark-OS browsers; chip backgrounds are dropped 2026-10-29 13:07:08 +00:00
Willem van den Ende edba66239e yak(coverage-floors-re-measure-and-re-record-after-the-behavior-test-yaks-mgr8): coverage floors: re-measure and re-record after the behavior-test yaks 2026-10-29 10:38:36 +00:00
Willem van den Ende 9c0f33e6ca yak(behavior-tests-web-controllers-and-auth-edge-cases-o04i): behavior tests: web controllers and auth edge cases 2026-10-29 10:15:21 +00:00
Willem van den Ende b2bd6bc3aa yak(behavior-tests-web-core-components-uyju): behavior tests: web core components 2026-10-29 10:14:27 +00:00
Willem van den Ende ef8407b272 yak(credo-strict-tuned-config-both-apps-x02d): credo strict, tuned config (both apps) 2026-10-29 10:13:02 +00:00
Willem van den Ende 13e3186f58 yak(boundary-repo-as-sibling-boundary-web-repo-forbidden-tgrc): boundary: Repo as sibling boundary, web->Repo forbidden 2026-10-29 10:08:56 +00:00
Willem van den Ende 3ef38bc8fc yak(mutation-testing-with-muex-blogex-pure-app-code-1341): mutation testing with muex (blogex + pure app code) 2026-10-29 10:07:55 +00:00
Willem van den Ende 09da15c1df yak(sobelow-in-the-lens-pass-vr40): sobelow in the Lens pass 2026-10-28 23:22:07 +00:00
Willem van den Ende e292b4ebfb yak(ratchets-deferred-decisions-handoff-practice-ccjp): ratchets, deferred decisions, handoff practice 2026-10-28 23:14:18 +00:00
Willem van den Ende b72446b80e yak(behavior-tests-editorial-liveviews-microprints-editor-dashboard-llh3): behavior tests: editorial LiveViews (microprints, editor dashboard) 2026-10-28 22:27:59 +00:00
Willem van den Ende 44a343ae72 yak(behavior-tests-blogex-rendered-html-components-layout-router-html-0dev): behavior tests: blogex rendered HTML (components, layout, router HTML) 2026-10-28 22:21:23 +00:00
Willem van den Ende cc03cefe8d yak(behavior-tests-blogex-public-api-and-router-plumbing-q1fb): behavior tests: blogex public API and router plumbing 2026-10-28 22:15:02 +00:00
Willem van den Ende c04d8c856c yak(link-validator-never-sees-compiled-html-internal-link-validation-is-vacuous-ddd1): Link validator never sees compiled HTML — internal-link validation is vacuous 2026-10-28 22:08:45 +00:00
Willem van den Ende 4e7798c906 yak(make-checkverify-format-gate-rewrites-files-instead-of-checking-lnje): make check/verify format gate rewrites files instead of checking 2026-10-28 21:50:01 +00:00
Willem van den Ende a6a7b3a8c7 yak(pin-user-inspect-output-exactly-both-redacted-fields-shape-l0mg): pin User inspect output exactly (both redacted fields + shape) 2026-10-27 21:28:37 +00:00
Willem van den Ende a72d73f21c app: logout disconnects open LiveView sockets
`put_token_in_session/2` wrote only `:user_token`, so `log_out_user/1`'s
`if live_socket_id = get_session(conn, :live_socket_id)` was always false and
logging out left open LiveView sockets running. Write the socket topic
alongside the token, copying phx.gen.auth's format verbatim
("users_sessions:" <> Base.url_encode64(token)) so it matches both what
Phoenix.LiveView.Socket.id/1 reads and what log_out_user/1 reads. The reissue
path goes through the same function, so it follows the new token.

Tests (provider/reader agreement, both previously absent):
- log_in_user/3 stores the id derived from the session token
- fetch_current_scope_for_user/2 reissues the id with the new token
- log_out_user/1 broadcasts exactly one "disconnect" on that id
- the value written is the topic Socket.id/1 returns for the session the
  websocket receives (string keys)

Not tested end to end: a real disconnect needs the transport process, which
Phoenix.LiveViewTest does not start — Phoenix.Socket.__info__/2 handles the
broadcast (deps/phoenix/lib/phoenix/socket.ex:571) and
phoenix_live_view/test/client_proxy.ex never subscribes to the socket id topic.

Gate: make check + make verify green (app coverage 86.97%, blogex 75.16%,
crap sensors green).

Yak: logout-must-disconnect-existing-liveview-sockets-livesocketid-never-set-6fme
2026-10-27 09:56:25 +00:00
Willem van den Ende 2ccbe28774 blogex: CRAP scores as a Lens sensor (ex_crap)
Same wiring as app: `crap` reads the export from `test.coverage`, runs in
MIX_ENV=test, and refuses to score when the export is absent.

Baseline at the default threshold (30): 12 files, 59 functions, worst score
6.00. Nothing to fix; blogex's CRAP ceiling is its coverage ceiling, which the
behavior-test yaks already own.
2026-10-26 21:32:39 +00:00
Willem van den Ende 2ba0b7bc48 app: CRAP scores as a Lens sensor (ex_crap)
The coverage export from `test.coverage` now also feeds a `crap` sensor,
second in app's VERIFY_SENSORS. It runs with MIX_ENV=test so it reads the
export's own environment instead of compiling a dev build and scoring a
different module set, and it guards on the export's existence because
ex_crap silently falls back to 0% coverage when the file is missing.

Baseline at ex_crap's default threshold (30): 23 files, 139 functions,
worst score 20.00 (CoreComponents.table/1, 0% - unused upstream scaffold).
No offenders, so nothing to refactor or cover yet.
2026-10-26 21:32:37 +00:00
Willem van den Ende 3eae3afcbb demo skill: look up routing and demo users in the project
Steps 2 and 4 carried copy-paste from another project (lib/hub_web/router.ex,
alias Hub.Accounts, /users/log-in with user[email]). A stale path there sends
the agent to a router that does not exist, and a guessed module alias fails at
run time.

Both are now generic instructions to read the project:

- route map: locate the router by searching for `use Phoenix.Router`, then read
  its scopes, pipelines and live_sessions to see what is public and what needs
  a session
- authentication: only when the route actually needs it; take credentials from
  documented demo accounts or the project's seeds, create a demo user through
  the project's own path (seeds, registration route, or the accounts function
  found by grep - never a guessed alias), and read the login route and form
  selectors off the router and rendered form

The port example keeps its concrete app/endpoint pair, now labelled as an
example with the grep that finds the right names, plus a note to use the
project's toolchain/build wrapper instead of bare `mix`.

make check green.
2026-10-26 21:26:19 +00:00
Willem van den Ende a0692056bc demo skill: read the dev port from config; make deps installs JS packages
The skill assumed port 4000, but config/dev.exs serves on 8056 (and honours
$PORT), so a fresh demo run failed its liveness check and every rodney URL
pointed at the wrong port.

- demo skill: resolve DEMO_PORT from the app config with
  `mix run --no-start -e 'Application.get_env(:firehose,
  FirehoseWeb.Endpoint)[:http][:port]'` (Endpoint.config/1 raises without a
  started endpoint), derive $DEMO_BASE_URL from it, start the server on that
  port from app/, and use $DEMO_BASE_URL in every rodney example. The skill
  also points at .tmp/phx-demo.log and names the assets/node_modules
  highlight.js failure as a diagnosable cause.
- app/Makefile: new `assets` target that runs `npm install` only when
  node_modules/.package-lock.json is missing, and `deps` now depends on it.
  A fresh clone gets a working JS bundle from `make deps`/`make test`; later
  runs never touch the network, so offline runs keep working.

Verified: guard skips with node_modules present and installs after moving it
aside; the skill's DEMO_PORT command prints 8056 and follows PORT=9999;
make check and make test (179 + 162) green.
2026-10-26 21:08:00 +00:00
Willem van den Ende be80c89548 demo: screenshots for the external link marker and its SR hint
Three captures of /blog/engineering/delayed-gratification-in-ai-summer
taken with rodney (headless chromium):

- external-link-hint-icon.png: a paragraph as rendered - the marker icon
  follows the external link, the hint text is invisible
- external-link-hint-unhidden.png: same paragraph with the sr-only rule
  cancelled, showing " (opens in a new tab)" inside the link, which is
  what screen readers get
- external-link-hint-page.png: full page for context (internal links such
  as "Swim the Test" carry no icon)

Backing evidence, from the same load: the accessibility tree names exactly
the 8 external links with "opens in a new tab"; the hint box measures
1x1 with clip-path inset(50%).
2026-10-26 20:54:45 +00:00
Willem van den Ende e3fc734862 External links: screen-reader hint on links that leave the blog
External links in post bodies already opened in a new tab and showed an
icon, but the icon is invisible to screen readers, so nothing announced
that the link leaves the site.

- blogex: ExternalLinks.rewrite/1 appends
  <span class="blogex-external-hint"> (opens in a new tab)</span> as the
  last child of every external anchor. No aria-label, so the accessible
  name still contains the visible link text (WCAG 2.5.3); the icon colour
  and 2.5.3 both hold. Idempotent: a second pass adds no second hint.
- blogex: anchors are now matched together with their closing tag
  (the hint goes inside), so a stray unclosed <a> is left alone.
- app: sr-only rule for .blogex-external-hint in .blogex-post-body. The
  split stays as before: blogex marks up, the host app styles.
- tests: hint cases in blogex (appended, per link, after inner markup,
  idempotent, not on internal/mailto/anchor links) + anchors without href
  (Blogex.ExternalLinks now at 100% line coverage), and an app test that
  the hint reaches the rendered post page. Two exact-string expectations
  updated for the intended markup change.

make test: app 179, blogex 162. make verify: green.
2026-10-26 20:28:16 +00:00
Willem van den Ende 4b4d0c491c app: exclude embed_templates wrappers and the release-only Application
Raises the measured total 86.17% -> 86.79% without touching a test, because the
excluded modules hold no behavior a test can observe:

- FirehoseWeb.{Blog,UserRegistration,UserSettings}HTML are `use` +
  `embed_templates` only; every function in them is macro-generated. Their
  TEMPLATES are already exercised through the controller tests.
- Firehose.Application: start/2 is proven by the suite booting at all, and
  config_change/3 runs only on a release hot upgrade.

Deliberately NOT excluded, despite being embed_templates wrappers:
FirehoseWeb.PageHTML (blog_label/1, post_base_path/1) and
FirehoseWeb.UserSessionHTML (local_mail_adapter?/0) carry real private logic, so
their sub-100% coverage is a behavior gap for the controller/auth yak, not macro
noise.

Also drops the now-dead Firehose.Checks.NoConnShadowing entry: lib_dev/ is no
longer compiled, so it is not measurable. Floor stays 86 (86.79 rounds down).
2026-10-26 20:02:45 +00:00
Willem van den Ende 9730987846 app: give credo checks one load path (fixes redefining-module warning)
lib_dev/ was both compiled (elixirc_paths for :dev and :test) and source-loaded
by credo through `requires:` in .credo.exs. Two load paths for one module means
that whenever _build/dev already holds the beam, `mix credo` warned:

    warning: redefining module Firehose.Checks.NoConnShadowing
    (current version loaded from _build/dev/.../Elixir.Firehose.Checks.NoConnShadowing.beam)

Keep the explicit contract and drop the accidental one: `requires:` is the
documented way for credo to load project checks, so lib_dev/ now leaves
elixirc_paths in every environment. Nothing under test/ referenced the check, so
the :test entry was dead as well, and credo compiles the file on every run
anyway, so the check still gets compiled — just once.

One-time migration for checkouts that already built it:
`mix clean` (or delete _build/dev) to drop the stale beam; until then the old
warning persists. Fresh clones are unaffected.
2026-10-26 20:01:33 +00:00
Willem van den Ende f54ce2fab7 app: note that CoreComponents.table/1 and list/1 are unused upstream scaffold
These two come from the upstream Phoenix generator and are worth keeping for
future data/admin screens, so they stay rather than being deleted as dead code.

The note also records why they look unused but aren't pointless: the collections
already in the UI are hand-rolled (editor_dashboard_live's drafts/scheduled rows
are bordered divs; Blogex.Components.post_index renders post previews and tag
lists itself). That is the fact a future reader needs before either deleting
these or writing yet another bespoke list.
2026-10-26 19:50:47 +00:00
Willem van den Ende cb13ccecab docs: the Lens pass runs each suite before its sensors
Same shape in both apps now that coverage is wired: one suite run exports the
coverage, then the sensors read it. AGENTS.md said "sensors ... and finally both
test suites" and told sensor authors only to list targets in VERIFY_SENSORS; add
the export dependency and the "a check must fail when its input is missing" rule,
since `mix test.coverage` exits 0 when it finds no export.
2026-10-26 18:41:02 +00:00
Willem van den Ende abb712f8a2 app: coverage floor (86) + framework-glue exclusions
Measured before choosing: 86.17%, identical via `mix test --cover` and via the
exported sample + `mix test.coverage`, stable across seeds. Floor is the integer
below that, with the rule written next to it: a FLOOR, ratcheted up as
behavior-test yaks land, never lowered (a drop means the change or an exclusion
is wrong).

Exclusions are the modules the suite cannot or should not exercise, each
justified in the config comment: `use`-generated glue (Repo, Mailer, Telemetry,
ErrorHTML/ErrorJSON), the `use FirehoseWeb` macro, production release tasks
(Release), the lib_dev credo check (NoConnShadowing), and test scaffolding
(DataCase, ConnCase, AccountsFixtures, Test.FakeBlog). Everything else counts,
CoreComponents/controllers/LiveViews included.

Wiring matches blogex and the fast/push split: `test.coverage` runs the suite
once with `--cover --export-coverage default` (deleting stale exports first,
since `mix test.coverage` imports every .coverdata it finds and would pool
them), then the `coverage` sensor enforces the floor from that export. The
sensor guards on the export existing because `mix test.coverage` exits 0 when it
finds nothing — a floor check that did not run must not pass.
2026-10-26 18:40:09 +00:00
Willem van den Ende 754c8fa1b9 blogex: coverage floor (74) + test-scaffolding exclusions
`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).
2026-10-26 18:38:35 +00:00
Willem van den Ende a9a02aa9ba app: hand-write the User Inspect impl so coverage can instrument it
`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.
2026-10-26 18:38:13 +00:00
Willem van den Ende 96278e0d02 AGENTS.md: document the fast loop, the Lens pass and its triage classes
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.
2026-10-26 17:59:44 +00:00
Willem van den Ende 5db2814c2e pre-push Lens pass + make hooks installer
.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.
2026-10-26 17:58:58 +00:00
Willem van den Ende 068911b4e8 make verify: add the Lens pass container (both apps, cheapest first)
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.
2026-10-26 17:58:42 +00:00
Willem van den Ende 0b815dc66a make check: include blogex in the cheap static gate
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.
2026-10-26 17:58:14 +00:00
Willem van den Ende ea588bd670 blogex: add precommit alias (compile --warning-as-errors, format, test) 2026-10-26 17:57:56 +00:00
Willem van den Ende 888aa8b00b blogex: format drift in link_validator (found before wiring blogex into make check) 2026-10-26 17:57:50 +00:00
Willem van den Ende cbbdaae53d doc: plan for internal code health sensors
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).
2026-10-26 17:47:04 +00:00
mostalive cee64b9f74 Script to sync Dockerfile and mise.toml 2026-10-26 16:48:42 +00:00
mostalive e16513f195 more precise pinning in docker and mise 2026-10-26 14:13:48 +00:00
mostalive 792516919a bump base image: elixir 1.20.4, debian 20 oct this month 2026-10-26 13:36:48 +00:00
Willem van den Ende a1ff6dbbac mix.lock: patch/minor dependency bumps (18 packages)
All within existing mix.exs constraints. Notables: bandit 1.12.5->1.13.1,
phoenix 1.8.15->16, phoenix_live_view 1.2.12->13, swoosh 1.28.1->1.29.0,
tailwind 0.4.1->0.5.1 (new bundled CLI - check CSS output after deploy).
Tests: 156 passing (app + blogex).
2026-10-26 12:53:33 +00:00
Firehose Bot 9fdb003521 converting to static site with one prompt 2026-10-23 09:32:40 +01:00
Firehose Bot d8cf81aeec mention I haven't tried Deepseek yet 2026-10-21 16:58:58 +01:00
Firehose Bot 5a9106b61e Merge branch 'main' of ssh://gitea.apps.sustainabledelivery.com:3022/mostalive/firehose 2026-10-21 14:53:50 +01:00
Firehose Bot aef57c54f9 my open weight models this month 2026-10-21 14:53:35 +01:00
mostalive 9050329685 Add link to fork, fix tags in other posts 2026-10-21 11:50:35 +01:00
mostalive 86d52aa420 add image to blog, fixed emacs drag and drop on linux 2026-10-21 11:19:31 +01:00
Firehose Bot ebf076c450 remove commas, fix description 2026-10-20 13:23:16 +01:00
Firehose Bot 628c032f0b Merge branch 'main' of ssh://gitea.apps.sustainabledelivery.com:3022/mostalive/firehose 2026-10-20 13:20:35 +01:00
Firehose Bot 8e1f5806cf video transcriber post 2026-10-20 13:17:50 +01:00