57 changes:
- A caller of
OutboundUrl::fetch() could not tell a 404 from a 200 — and the framework's own
caller was storing the placeholder
- The
tokenactions self-repair could not run on MySQL
- What the email second factor does when the store or the mailer fails
- The class added this morning was the day's biggest coverage gap — and
addRemoteImage() with it
- Who counts as signed in for the administration area, and what a missing page answers with
- A grouped page could not be asked for at all
- Where the write spool buffers, and how long it keeps trying
- Re-running
create:model destroyed the model
- The Redis operations that only Redis has
- …and the same reconnect on the path most statements take
- The Select2 branch of the CRUD generator
- Revoking one device, and the cascade that is MySQL-only
- What each DevPanel card shows when the thing behind it is missing
- A security-notification class at 6% covered
- The per-address rate limit had never run
- The session upsert had never been issued against PostgreSQL
- What a client is told about every MCP tool before it calls one
- A round of 69 tests that moved coverage by one statement
route-list was tested through its parser and never through itself
- The two scheduled commands whose
execute() had no test at all
- The scaffolders' refusals, and two things that cannot be tested the obvious way
MediaObject's error arms, and a dead-code finding that was not one
- An application's own
User class was never once returned in a test
- Losing Redis would have taken the application down, not the cache
- The Memcached counter had never run, and
clear() empties more than you think
- What the human check does when it breaks, and the top three targets that cannot be reached
- The Greek in the search box had never been tested
- The URL cache that keeps a worker's memory bounded
ST_MakePoint() takes longitude first, and nothing had ever checked
- The deadlock retry, which had never retried anything
- The privilege boundary in the admin area, and a green run that proved nothing
- A better index: 48 methods with no covered line at all
- TOTP replay protection, which had never run and stands down three ways
- The webhook credential check, and the fourth test that replaced what it tested
- The second leg of an API login had never been taken
- The account a client-credentials token hangs on, and a one-character invariant
--spa-components and the two conditions that silently decide nothing happens
- 453 statements were being excluded from measurement by accident
- Three small classes off the never-run list, and a test that told me it was empty
- What
auth:twofactor-cleanup actually sweeps, and the assertion total I made unreadable
- A test called "the omnibox limit is capped" that would pass on an uncapped omnibox
- Configuring a local asset list crashed every page build
- An addon setting called
2fa_enabled is not called that
- The query behind
hasIndex(), and the assertion that PostgreSQL does not inherit it
- Three counters, sessions and headers — and a setter that means the opposite of its name
- Seven more off the never-run list, in one pass
- Three more, and a state leak I caused and had already written the guide entry for
- Three small ones to close the gap
- The machine account, an MCP call's identity, and a helper the framework ships untested
/adminer refuses with a 404, and why that is the security decision
- The reconnect that must forget, and the first sign-in that is not a new device
- The middleware list, and the bracket somebody will forget
can() and cannot() — the pair every guard clause is written with
- The last three: two seams and the accessor every closing test reads
- The two gaps that were in the environment, not the tests
34 changes:
- The half of OAuth discovery that was missing
- Three components that were nearly accessible
- The Model Context Protocol, over HTTP, for somebody else's assistant
- Offering an MCP capability without writing a class
- A page head that stops claiming things the page never said
- What the site tells a machine that arrives uninvited
- A keyboard can get past the navigation, and the consent screen stops phoning out
- A token outlived the client that issued it, and kept working
- An error log that was mostly not errors
- A client secret nobody had to present
- The SMTP password was readable to anyone who could read the database
- The webhook signing key and the TOTP seed, likewise
- A tenant scope that held until you asked for page 1
- A grant on one record opened the whole collection
- The session cookie lost its
secure flag behind every TLS-terminating proxy
- The legacy CSRF check compared tokens with
===
- A LIMIT built by string concatenation
addslashes() as an escaping fallback, and as a PHP-literal encoder
- The README described a framework this is not
- The schema promised tenant isolation nobody implements
- Joins on two columns, and aliases that survive resolution
- Permissions can now be resolved within one organisation
- Roles had two tables, a permissions screen, and no way to make one
- The client secret is hashed, and the realtime key moves out of its way
- A public client can say it is one
- The roles screen had a menu entry and no address
- A guard that read a variable that did not exist (FW-047)
- A pager that could not be told to stop counting (FW-048)
- Two components no theme could style
SchemaBuilder::hasIndex()
- Tokens are encrypted at rest, and matched on a digest
27 changes:
- Two invisible columns, and a row of chips that ran off the panel
- Reading a sent message back: what it carried, and what came of it
- Web push: a notification on a device whose browser is closed
- The coverage gate reported 100% on a change that added an untested package
- Sending one account a message, on the channels it can actually receive
- Mass messages: an audience worth choosing, and push that reaches people
- The line the inbox shows, dark mode, and what DNS says about your mail
- Three MCP tools for the questions that were being answered by failing
- A log viewer in the DevPanel, at the address that used to 400
- The mail log grows without limit, and deleting it is the wrong fix
- The drift check's own false alarms, and the panel that had never worked
- The email preview's findings were taking a third of the width from the message
- Push had five parts and shipped four of them
- Every browser restart minted a session, and none of the old ones ever ended
- Nothing knew what kinds of mail an application sends
- Choosing who a mass message goes to, and seeing who that is
- The soft prompt was rendered on every page and never shown
- Nothing recorded a sent push
- The DevPanel had a second log viewer because of one hard-coded URL
- The database tab could not answer a developer's question about a database
- An unread message was something you had to go looking for
- Two development query logs that grew until the process died
- The database tab, again: chunks, and the half of the admin screen it still lacked
- Twenty-five forked PHP processes, for a constant
- The database tab, third time: copied rather than rewritten
- An unsubscribe is two records, and the second had nowhere to go
- The coverage gate, and the two bugs it found
66 changes:
- A sign-in is questioned when it looks wrong, not when the browser is new
- One TOTP code, one login
- A password change that cannot be the same password
- The bundled sign-in forms can price automation
- The mail an account was sent is on the account's screen
- Themed email: the column that had never been read
- An email is written in the recipient's language
- A datatable over an
authserver.* table read as empty on MySQL
- Authorization is three layers, and the guide said two
- Two reported bugs: a date of zero, and a cropped PNG's black corners
- Something runs the second-factor cleanups
- The services screen says whether anything is listening to its buttons
- A message to many accounts, composed and sent from a screen
- Three test classes were spending their time emptying the cache
- Requiring a second factor, and requiring a real one
- The debug bar says where the second factor stands — and stops forgetting your tab
- Three dead ends on the administration screens
- A settings row no longer opens the debug toolbar
- Nothing on the settings screen opens the DevPanel any more
- A scaffolded application comes with something that runs its background work
/admin/Services says how to create the supervisor
- New-sign-in alerts can be on unless turned off
- The DevPanel's Back button goes where you came from
Session can write, not only read
- One apostrophe no longer destroys a page's breadcrumb structured data
Html\Date renders the time and the dropdowns it always claimed to accept
- The queue worker runs under a supervisor
/admin/Services can see a supervisor in another container
- An unsubscribe link, and the two headers Gmail actually reads
Addon::trigerAddon() refuses a nameless addon too
- The default language is a list, not ten characters of free text
- Adminer, at
/adminer, behind the application's own gate
/messages — the inbox those internal messages were going into
- Any browser with JavaScript can solve the human check — and a test proves it
- A CSP-blocked redirect, and a script with two nonces
- Adminer signs itself in
Html\Date reads the properties it declares
- An idle connection is not a query running for three hours
Html\Date's field is validated by the browser again
- A date is written the way the language writes dates
- Two of the widest columns on the process list said the same thing four times
- Two empty boxes where the log charts should be
- The log dashboard's figures, asked for by something that is not a screen
- The components guide listed
Seo and then never mentioned it again
mcp:serve had its own copy of the tool catalogue, and it was stale
- The most frequent error in the log was the framework asking a question
- Every log entry was dated the moment you looked at it
mcp:serve is not something a person could debug
- An MCP tab in the DevPanel: the schema as a form, the answer on the page
find-symbol: the question grep cannot answer
- The DevPanel's MCP tab shipped with a JavaScript syntax error
route-list executed the views, and then said there were no routes
- A link in the DevPanel is styled wherever it is
- Two more MCP tools: what the CLI can do, and what the theme is made of
- Three tests that were a copy of the tool catalogue
api-docs and find-tests: the other two of the four
- Two rules that could not be checked, and now can
changelog-add: the one tool that writes
- The plain-text part of an email was the CSS, with the links removed
- Four headers that decide what happens to a message
- Gmail actions: a button in the message list, and the reason yours is not showing
- A ViewAction never needed a handler — the password-reset mail has one now
- One-click mail actions, and the handler a "this wasn't me" button needs
- The unsubscribe page was 181 KB, and 180 of them were the website
- A session count that was not a number, and four tables called
sessions
- Email tracking that works, and only for mail somebody agreed to receive
15 changes:
- Wrote 1 path(s), and every word of it was true
- The JSON renderer decided every response was fine
- A failed list query killed the request
- The controller a model needs costs 1.5 microseconds
- The one place a shared layout could not be
- A comparison table is a claim too
- The redirect the guide promised
- The cache category was accepted and discarded
- A marker nothing flipped
- The HTML document could not say what page it was
- getData() and the columns it never returned
- An alarm that stays rare
- A column that described data nobody wrote
- Fourteen tabs do not fit
- Two things that did not complain
2 changes:
- One quote in a station name
- Two flags that never met
39 changes:
- The server now says where its own time went
- An API request is not a browser session
auth:unlock — lifting a lockout you gave yourself
- The toolbar answers "who am I, and until when"
- A parent class for services, and a tab that admits they exist
- An Errors tab, for what the browser threw
- A Client tab: what the browser thinks the world is
- The page outranks the toolbar
- An API playground in the toolbar — and a doubled slash it found
- A WebSocket daemon that can read a Redis stream
- The body of a DELETE request
- A
? in a raw fragment now binds where it was written
init will not scaffold over your application
- Debug data for an application that does not use the API layer
- The other way to lose a page
- The toolbar injects through the response, not an output buffer
- Two build settings that failed quietly
- Three dead stubs, and four corrections to older posts
scaffold:spa — a front end for an application that already exists
- The MCP server says which project it is
- Typed endpoints, generated from the document
- Where the suite's fifteen minutes actually go
- A shared document, seven eight-second tests, and a
? in the bar
- A scaffolded application was set up to learn it the hard way
- The suite was running
composer update sixty-one times
- The test database was afraid of losing data
TRUNCATE is slower than dropping the table
- The comment said
// product 1 = Apple
- The same change made PostgreSQL slower
- A suite that only passed in one order
- The guide described an API nobody had built
- The widget area that rendered nothing
maxRuntime was a range, and it read like a number
- An empty ban list is still a ban list
- The ingest dropped the id it had just read
- Which rule said no
- Four corrections from the other side of the boundary
- "Minor variable name changes"
- A blank page is not an error
39 changes:
- The server now says where its own time went
- An API request is not a browser session
auth:unlock — lifting a lockout you gave yourself
- The toolbar answers "who am I, and until when"
- A parent class for services, and a tab that admits they exist
- An Errors tab, for what the browser threw
- A Client tab: what the browser thinks the world is
- The page outranks the toolbar
- An API playground in the toolbar — and a doubled slash it found
- A WebSocket daemon that can read a Redis stream
- The body of a DELETE request
- A
? in a raw fragment now binds where it was written
init will not scaffold over your application
- Debug data for an application that does not use the API layer
- The other way to lose a page
- The toolbar injects through the response, not an output buffer
- Two build settings that failed quietly
- Three dead stubs, and four corrections to older posts
scaffold:spa — a front end for an application that already exists
- The MCP server says which project it is
- Typed endpoints, generated from the document
- Where the suite's fifteen minutes actually go
- A shared document, seven eight-second tests, and a
? in the bar
- A scaffolded application was set up to learn it the hard way
- The suite was running
composer update sixty-one times
- The test database was afraid of losing data
TRUNCATE is slower than dropping the table
- The comment said
// product 1 = Apple
- The same change made PostgreSQL slower
- A suite that only passed in one order
- The guide described an API nobody had built
- The widget area that rendered nothing
maxRuntime was a range, and it read like a number
- An empty ban list is still a ban list
- The ingest dropped the id it had just read
- Which rule said no
- Four corrections from the other side of the boundary
- "Minor variable name changes"
- A blank page is not an error
14 changes:
- Late writes into compressed chunks no longer have to be lost
print replaces the pdf document type, which had not worked for years
- MariaDB stops pretending to be MySQL
- The toolbar now sees what a page does after it renders, and can be opened on a live server
- Nine queries per request, most of them avoidable
- The SPA debug panel now says it exists
- The docs now say when you would need them
- The toolbar's hide button now hides the toolbar
spa:dev / spa:build, and a service container that actually exists
- One toolbar, delivered two ways — and the SPA panel gains every tab
- A name for every request, and the log lines it wrote
- The server-rendered toolbar now uses the one renderer too
- Settings read every key one at a time on PostgreSQL, and said nothing
- SSE events published during a reconnect are no longer lost
6 changes:
- Five rolled-up views were frozen on every database without TimescaleDB
- A model may give
load() whatever parameters it needs
- Five developer panels had been empty for years, and nothing said why
- The rate limiter counted the proxy, and counted it badly
- A human check that prices spam instead of pretending to detect it
getInstance() is a factory, and two call sites forgot
16 changes:
pramnos init checks both Docker ports before proposing one
init scaffolds a working SPA — Svelte + daisyUI, Vite, Vitest and Docker
- Files the container writes now belong to you, not to root
- SPA follow-ups: the dev server, the site root, and the docs generator
- The FK migration stops assuming the schema of tables it does not own
- The scaffolded SPA now actually talks to the API
create:crud builds the SPA half too
- A debug bar a SPA can actually use — and a guide for testing the front end
- Generated CRUD gets real authorisation — and
delete gets any at all
- A SPA project gets an administration screen
cache:clear no longer wipes every installation sharing the backend
- Signing in works, screens have URLs, and the SPA wears the project's colours
Permissions answers from whichever store the installation has
- One permission system, available to every project with users
timescale:ensure — repairing hypertables on a database that got TimescaleDB late
- The GDPR endpoints queried a table that does not exist