# Revision history for PAGI-Tools

0.002001 - 2026-06-30
  - PAGI::Endpoint / PAGI::Endpoint::Router: route-level event middleware is now
    spec-pure factory form, composed at build time. The vestigial call() and the
    old 4-arg ($scope, $receive, $send, $next) middleware form are removed; the
    endpoint owns the value-flow chain and responds at the framework boundary
    (await $next->() returns the handler's response value). WS/SSE route-level
    middleware now fail loudly (unsupported), and a value-flow non-response error
    names the offending method.
  - PAGI::SSE: close() now sends an explicit sse.close event (with an optional
    server-side reason) and wakes a parked run(); close() is async so on_close
    callbacks run to completion.
  - PAGI::Endpoint::SSE: await keepalive() so the sse.keepalive event is actually
    emitted on a fully-async server.
  - PAGI::Utils::is_response: one predicate for "did the handler return a
    response?", shared by the endpoint and router dispatch paths so the
    diagnostics stay consistent.
  - PAGI::SSE / PAGI::Context::SSE: add is_connected, derived from the SSE
    wrapper's own state machine (true while started and not closed), mirroring
    PAGI::WebSocket. Context::SSE::is_connected overrides the base method, which
    reads pagi.connection (not applicable to sse) and therefore wrongly reported
    a live stream as disconnected. Like the WebSocket counterpart it is "not
    falsely stale" rather than a live network probe.
  - PAGI::WebSocket->deny() no longer records the HTTP denial status as a
    WebSocket close code; close_code is undef after an HTTP denial (a denial
    sends a response, not a close frame). The bare-403 fallback still reports
    1008.
  - PAGI::WebSocket try_send_text/bytes/json no longer fabricate a 1006
    "Connection lost" close (and no longer mark the socket closed) when a send
    errors. A failed send returns false without mutating connection state; a
    real disconnect is still detected via the websocket.disconnect event.
  - Docs: PAGI::SSE scope caching described accurately (cached while
    referenced via a weak ref, not a guaranteed singleton);
    PAGI::Request::Negotiate content-negotiation example reads the Accept
    header through PAGI::Request instead of the (fatal) $scope->{headers}{...};
    try_send_* documented as best-effort with a send-method ladder; a cookbook
    recipe on declining an SSE request (404 / 401 / 204 stop-reconnect);
    examples and Endpoint POD migrated off the removed 4-arg middleware form.
  - REMOVED PAGI::Middleware::WebSocket::Compression. permessage-deflate
    (RFC 7692) is a WebSocket framing-layer feature -- a compressed message is
    signalled by the RSV1 frame bit, which only the server's framing layer can
    set. A middleware at the PAGI event layer can deflate the payload bytes but
    cannot flag the frame, so the deflated bytes went out in a normal RSV1=0
    frame (garbage on the wire). It also wrote a non-spec `extensions` key on
    websocket.accept (the event allows only `subprotocol`/`headers`), which the
    reference server ignores -- and that server explicitly does not implement
    permessage-deflate (RSV bits forced to 0). If WebSocket compression is
    wanted, it must be a server feature, not an application middleware.
  - PAGI::App::Router: an unmatched SSE or WebSocket route now returns a real
    404 instead of crashing the connection. The 404 is emitted with the event
    family that matches the scope -- sse.http.response.* on SSE and
    websocket.http.response.* on WebSocket, plain http.response.* on HTTP --
    since those scopes reject a plain http.response.* before a stream or
    handshake starts. A custom not_found app still takes over for every scope
    type. Requires a server supporting the sse.http.response.* decline events
    (PAGI-Server 0.002005+).

0.002000 - 2026-06-26
  - Split out of the PAGI distribution into its own distribution (git history
    preserved).
  - New PAGI::Tools module anchors the distribution.
  - The application runner ships in PAGI-Server as PAGI::Server::Runner.
  - B<BREAKING>: PAGI::Response is now a value — factories build detached
    responses, body methods return $self, respond($send) sends, to_app makes any
    response mountable; Endpoint HTTP handlers RETURN a response value
    (await $res->json(...) no longer sends).
  - B<BREAKING>: PAGI::Request->configure/->config removed; path_param strict and
    multipart limits are now per-call options.
  - PAGI::Headers: ordered, case-insensitive, multi-value header container
    (rejects undef values; to_hash); Response and Request route headers through it.
  - is_sent now reads response_started off the pagi.connection object, not a scope
    scalar, so it survives middleware's scope copy. Adds PAGI::Test::ConnectionState.
  - PAGI::Response->has_body_source: predicate for whether a body source was
    registered.
  - PAGI::Response: charset follows the body — text/html/send gain
    "; charset=utf-8", json() stays bare application/json.
  - PAGI::Utils::to_app coerces coderefs, component objects, and class names; every
    composition point accepts anything it accepts.
  - builder's enable() accepts configured middleware instances.
  - PAGI::App::Router: coderef route middleware can transform the channel.
  - PAGI::Endpoint::Router route middleware are value-flow (await $next->() returns
    the handler's response); App::Router dispatch returns the matched route's value.
  - PAGI::Context: $ctx->text/html/json/redirect shorthands,
    assert_http/websocket/sse guards, on_default catch-all, raw_send accessor.
  - Backpressure/lifecycle helpers: on_complete, buffered_amount/high_water_mark/
    low_water_mark, on_high_water/on_drain/is_writable, WebSocket->deny().
  - PAGI::Request->multipart_stream: pull-based streaming multipart parsing with
    app-controlled sinks; mutually exclusive with the buffered body methods.
  - PAGI::App::Redirect and ::NotFound build a PAGI::Response value (modules kept
    for the dynamic case).
  - PAGI::Lifespan coerces its app arg via to_app, and reports shutdown-handler
    failures instead of swallowing them.
  - Spec: URLMap sets root_path; WrapCGI builds SCRIPT_NAME from root_path.
  - Canonical scope-adding middleware route through the modify_scope helper.
  - Documented the PAGI::Response subclassing seam for framework authors.
  - Docs: recipes are now PAGI::Tools::Cookbook; Tutorial/Cookbook examples
    corrected against the current API; chat-showcase example uses PAGI::App::Router
    + PAGI::Lifespan.
  - For changes prior to 0.002000, see the Changes file of the PAGI distribution
    (versions up to 0.001023).
