[{"name":"ApiRoute","comment":" ApiRoute's are defined in `src/Api.elm` and are a way to generate files either statically pre-rendered at build-time (like RSS feeds, sitemaps, or any text-based file that you output with an Elm function),\nor server-rendered at runtime (like a JSON API endpoint, or an RSS feed that gives you fresh data without rebuilding your site).\n\nYour ApiRoute's get access to a [`BackendTask`](BackendTask) so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in\nthe BackendTask for your ApiRoutes and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.\n\nSimilar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different\nuse cases they support.\n\n\n## Pre-Rendering\n\nA pre-rendered ApiRoute is just a generated file. For example:\n\n  - [An RSS feed](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/docs/app/Api.elm#L77-L84) ([Output file](https://elm-pages.com/blog/feed.xml))\n  - [A calendar feed in the ical format](https://github.com/dillonkearns/incrementalelm.com/blob/d4934d899d06232dc66dcf9f4b5eccc74bbc60d3/src/Api.elm#L51-L60) ([Output file](https://incrementalelm.com/live.ics))\n  - A redirect file for a hosting provider like Netlify\n\nYou could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination!\nThe beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!\n\n@docs single, preRender\n\n\n## Server Rendering\n\nYou could use server-rendered ApiRoutes to do a lot of similar things, the main difference being that it will be served up through a URL and generated on-demand when that URL is requested.\nSo for example, for an RSS feed or ical calendar feed like in the pre-rendered examples, you could build the same routes, but you would be pulling in the list of posts or calendar events on-demand rather\nthan upfront at build-time. That means you can hit your database and serve up always-up-to-date data.\n\nNot only that, but your server-rendered ApiRoutes have access to the incoming HTTP request payload just like your server-rendered Route Modules do. Just as with server-rendered Route Modules,\na server-rendered ApiRoute accesses the incoming HTTP request through a [Server.Request.Parser](Server-Request). Consider the use cases that this opens up:\n\n  - Serve up protected assets. For example, gated content, like a paid subscriber feed for a podcast that checks authentication information in a query parameter to authenticate that a user has an active paid subscription before serving up the Pro RSS feed.\n  - Serve up user-specific content, either through a cookie or other means of authentication\n  - Look at the [accepted content-type in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and use that to choose a response format, like XML or JSON ([full example](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/end-to-end/app/Api.elm#L76-L107)).\n  - Look at the [accepted language in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) and use that to choose a language for the response data.\n\n@docs serverRender\n\nYou can also do a hybrid approach using `preRenderWithFallback`. This allows you to pre-render a set of routes at build-time, but build additional routes that weren't rendered at build-time on the fly on the server.\nConceptually, this is just a delayed version of a pre-rendered route. Because of that, you _do not_ have access to the incoming HTTP request (no `Server.Request.Parser` like in server-rendered ApiRoute's).\nThe strategy used to build these routes will differ depending on your hosting provider and the elm-pages adapter you have setup, but generally ApiRoute's that use `preRenderWithFallback` will be cached on the server\nso within a certain time interval (or in the case of [Netlify's DPR](https://www.netlify.com/blog/2021/04/14/distributed-persistent-rendering-a-new-jamstack-approach-for-faster-builds/), until a new build is done)\nthat asset will be served up if that URL was already served up by the server.\n\n@docs preRenderWithFallback\n\n\n## Defining ApiRoute's\n\nYou define your ApiRoute's in `app/Api.elm`. Here's a simple example:\n\n    module Api exposing (routes)\n\n    import ApiRoute\n    import BackendTask exposing (BackendTask)\n    import FatalError exposing (FatalError)\n    import Server.Request\n\n    routes :\n        BackendTask FatalError (List Route)\n        -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n        -> List (ApiRoute.ApiRoute ApiRoute.Response)\n    routes getStaticRoutes htmlToString =\n        [ preRenderedExample\n        , requestPrinterExample\n        ]\n\n    {-| Generates the following files when you\n    run `elm-pages build`:\n\n      - `dist/users/1.json`\n      - `dist/users/2.json`\n      - `dist/users/3.json`\n\n    When you host it, these static assets will\n    be served at `/users/1.json`, etc.\n\n    -}\n    preRenderedExample : ApiRoute.ApiRoute ApiRoute.Response\n    preRenderedExample =\n        ApiRoute.succeed\n            (\\userId ->\n                BackendTask.succeed\n                    (Json.Encode.object\n                        [ ( \"id\", Json.Encode.string userId )\n                        , ( \"name\", \"Data for user \" ++ userId |> Json.Encode.string )\n                        ]\n                        |> Json.Encode.encode 2\n                    )\n            )\n            |> ApiRoute.literal \"users\"\n            |> ApiRoute.slash\n            |> ApiRoute.capture\n            |> ApiRoute.literal \".json\"\n            |> ApiRoute.preRender\n                (\\route ->\n                    BackendTask.succeed\n                        [ route \"1\"\n                        , route \"2\"\n                        , route \"3\"\n                        ]\n                )\n\n    {-| This returns a JSON response that prints information about the incoming\n    HTTP request. In practice you'd want to do something useful with that data,\n    and use more of the high-level helpers from the Server.Request API.\n    -}\n    requestPrinterExample : ApiRoute ApiRoute.Response\n    requestPrinterExample =\n        ApiRoute.succeed\n            (\\pageId revisionId request ->\n                Encode.object\n                    [ ( \"pageId\"\n                      , Encode.string pageId\n                      )\n                    , ( \"revisionId\"\n                      , Encode.string revisionId\n                      )\n                    , ( \"body\"\n                      , request\n                            |> Server.Request.body\n                            |> Maybe.map Encode.string\n                            |> Maybe.withDefault Encode.null\n                      )\n                    , ( \"method\"\n                      , request\n                            |> Server.Request.method\n                            |> Server.Request.methodToString\n                            |> Encode.string\n                      )\n                    , ( \"cookies\"\n                      , request\n                            |> Server.Request.cookies\n                            |> Encode.dict\n                                identity\n                                Encode.string\n                      )\n                    , ( \"queryParams\"\n                      , request\n                            |> Server.Request.queryParams\n                            |> Encode.dict\n                                identity\n                                (Encode.list Encode.string)\n                      )\n                    ]\n                    |> Response.json\n                    |> BackendTask.succeed\n            )\n            -- Path: /pages/:pageId/revisions/:revisionId/request-test\n            |> ApiRoute.literal \"pages\"\n            |> ApiRoute.slash\n            |> ApiRoute.capture\n            |> ApiRoute.slash\n            |> ApiRoute.literal \"revisions\"\n            |> ApiRoute.slash\n            |> ApiRoute.capture\n            |> ApiRoute.slash\n            |> ApiRoute.literal \"request-test\"\n            |> ApiRoute.serverRender\n\n@docs ApiRoute, ApiRouteBuilder, Response\n\n@docs capture, literal, slash, succeed\n\n\n## Including Head Tags\n\n@docs withGlobalHeadTags\n\n\n## Internals\n\n@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask\n\n","unions":[],"aliases":[{"name":"ApiRoute","comment":" ","args":["response"],"type":"Internal.ApiRoute.ApiRoute response"},{"name":"ApiRouteBuilder","comment":" The intermediary value while building an ApiRoute definition.\n","args":["a","constructor"],"type":"Internal.ApiRoute.ApiRouteBuilder a constructor"},{"name":"Response","comment":" The final value from defining an ApiRoute.\n","args":[],"type":"Json.Encode.Value"}],"values":[{"name":"capture","comment":" Captures a dynamic segment from the route.\n","type":"ApiRoute.ApiRouteBuilder (String.String -> a) constructor -> ApiRoute.ApiRouteBuilder a (String.String -> constructor)"},{"name":"getBuildTimeRoutes","comment":" For internal use by generated code. Not so useful in user-land.\n","type":"ApiRoute.ApiRoute response -> BackendTask.BackendTask FatalError.FatalError (List.List String.String)"},{"name":"getGlobalHeadTagsBackendTask","comment":" For internal use.\n","type":"ApiRoute.ApiRoute response -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag))"},{"name":"literal","comment":" A literal String segment of a route.\n","type":"String.String -> ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"preRender","comment":" Pre-render files for a given route pattern statically at build-time. If you only need to serve a single file, you can use [`single`](#single) instead.\n\n    import ApiRoute\n    import BackendTask\n    import BackendTask.Http\n    import Json.Decode as Decode\n    import Json.Encode as Encode\n\n    starsApi : ApiRoute ApiRoute.Response\n    starsApi =\n        ApiRoute.succeed\n            (\\user repoName ->\n                BackendTask.Http.getJson\n                    (\"https://api.github.com/repos/\" ++ user ++ \"/\" ++ repoName)\n                    (Decode.field \"stargazers_count\" Decode.int)\n                    |> BackendTask.allowFatal\n                    |> BackendTask.map\n                        (\\stars ->\n                            Encode.object\n                                [ ( \"repo\", Encode.string repoName )\n                                , ( \"stars\", Encode.int stars )\n                                ]\n                                |> Encode.encode 2\n                        )\n            )\n            |> ApiRoute.literal \"repo\"\n            |> ApiRoute.slash\n            |> ApiRoute.capture\n            |> ApiRoute.slash\n            |> ApiRoute.capture\n            |> ApiRoute.slash\n            |> ApiRoute.literal \".json\"\n            |> ApiRoute.preRender\n                (\\route ->\n                    BackendTask.succeed\n                        [ route \"dillonkearns\" \"elm-graphql\"\n                        , route \"dillonkearns\" \"elm-pages\"\n                        ]\n                )\n\nYou can view these files in the dev server at <http://localhost:1234/repo/dillonkearns/elm-graphql.json>, and when you run `elm-pages build` this will result in the following files being generated:\n\n  - `dist/repo/dillonkearns/elm-graphql.json`\n  - `dist/repo/dillonkearns/elm-pages.json`\n\nNote: `dist` is the output folder for `elm-pages build`, so this will be accessible in your hosted site at `/repo/dillonkearns/elm-graphql.json` and `/repo/dillonkearns/elm-pages.json`.\n\n","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"preRenderWithFallback","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"serverRender","comment":" ","type":"ApiRoute.ApiRouteBuilder (Server.Request.Request -> BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"single","comment":" Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because\nsince there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.\n","type":"ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) (List.List String.String) -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"slash","comment":" A path separator within the route.\n","type":"ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"succeed","comment":" Starts the definition of a route with any captured segments.\n","type":"a -> ApiRoute.ApiRouteBuilder a (List.List String.String)"},{"name":"toJson","comment":" Turn the route into a pattern in JSON format. For internal uses.\n","type":"ApiRoute.ApiRoute response -> Json.Encode.Value"},{"name":"withGlobalHeadTags","comment":" Include head tags on every page's HTML.\n","type":"BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag) -> ApiRoute.ApiRoute response -> ApiRoute.ApiRoute response"}],"binops":[]},{"name":"BackendTask","comment":" In an `elm-pages` app, each Route Module can define a value `data` which is a `BackendTask` that will be resolved **before** `init` is called. That means it is also available\nwhen the page's HTML is pre-rendered during the build step. You can also access the resolved data in `head` to use it for the page's SEO meta tags.\n\nA `BackendTask` lets you pull in data from:\n\n  - Local files ([`BackendTask.File`](BackendTask-File))\n  - HTTP requests ([`BackendTask.Http`](BackendTask-Http))\n  - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`BackendTask.Glob`](BackendTask-Glob))\n  - Ports, i.e. getting JSON data from running custom NodeJS, similar to a port in a vanilla Elm app except run at build-time in NodeJS, rather than at run-time in the browser ([`BackendTask.Custom`](BackendTask-Custom))\n  - Hardcoded data (`BackendTask.succeed \"Hello!\"`)\n  - Or any combination of the above, using `BackendTask.map2`, `BackendTask.andThen`, or other combining/continuing helpers from this module\n\n\n## BackendTask's vs. Effect's/Cmd's\n\nBackendTask's are always resolved before the page is rendered and sent to the browser. A BackendTask is never executed\nin the Browser. Instead, the resolved data from the BackendTask is passed down to the Browser - it has been resolved\nbefore any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,\nand for server-rendered routes its BackendTask is resolved on the server.\n\nEffect's/Cmd's are never executed on the CLI or server, they are only executed in the Browser. The data from a Route Module's\n`init` function is used to render the initial HTML on the server or build step, but the Effect isn't executed and `update` is never called\nbefore the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,\nand a nicely typed way to define the initial `Data` you have to render your initial view.\n\nBecause `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.\nSo why not just get the data the old-fashioned way, with `elm/http`, for example?\n\nA few reasons:\n\n1.  BackendTask's allow you to pull in data that you wouldn't normally be able to access from an Elm app, like local files, or listings of files in a folder. Not only that, but the dev server knows to automatically hot reload the data when the files it depends on change, so you can edit the files you used in your BackendTask and see the page hot reload as you save!\n2.  You can pre-render HTML for your pages, including the SEO meta tags, with all that rich, well-typed Elm data available! That's something you can't accomplish with a vanilla Elm app, and it's one of the main use cases for elm-pages.\n3.  Because `elm-pages` has a build step, you know that your `BackendTask.Http` requests succeeded, your decoders succeeded, your custom BackendTask validations succeeded, and everything went smoothly. If something went wrong, you get a build failure and can deal with the issues before the site goes live. That means your users won't see those errors, and as a developer you don't need to handle those error cases in your code! Think of it as \"parse, don't validate\", but for your entire build. In the case of server-rendered routes, a BackendTask failure will render a 500 page, so more care needs to be taken to make sure all common errors are handled properly, but the tradeoff is that you can use BackendTask's to pull in highly dynamic data and even render user-specific pages.\n4.  For static routes, you don't have to worry about an API being down, or hitting it repeatedly. You can build in data and it will end up as optimized binary-encoded data served up with all the other assets of your site. If your CDN (static site host) is down, then the rest of your site is probably down anyway. If your site host is up, then so is all of your `BackendTask` data. Also, it will be served up extremely quickly without needing to wait for any database queries to be performed, `andThen` requests to be resolved, etc., because all of that work and waiting was done at build-time!\n\n\n## Mental Model\n\nYou can think of a BackendTask as a declarative (not imperative) definition of data. It represents where to get the data from, and how to transform it (map, combine with other BackendTasks, etc.).\n\n\n## How do I actually use a BackendTask?\n\nThis is very similar to Cmd's in Elm. You don't perform a Cmd just by running that code, as you might in a language like JavaScript. Instead, a Cmd _will not do anything_ unless you pass it to The Elm Architecture to have it perform it for you.\nYou pass a Cmd to The Elm Architecture by returning it in `init` or `update`. So actually a `Cmd` is just data describing a side-effect that the Elm runtime can perform, and how to build a `Msg` once it's done.\n\n`BackendTask`'s are very similar. A `BackendTask` doesn't do anything just by \"running\" it. Just like a `Cmd`, it's only data that describes a side-effect to perform. Specifically, it describes a side-effect that the _elm-pages runtime_ can perform.\nThere are a few places where we can pass a `BackendTask` to the `elm-pages` runtime so it can perform it. Most commonly, you give a field called `data` in your Route Module's definition. Instead of giving a `Msg` when the side-effects are complete,\nthe page will render once all of the side-effects have run and all the data is resolved. `elm-pages` makes the resolved data available your Route Module's `init`, `view`, `update`, and `head` functions, similar to how a regular Elm app passes `Msg`'s in\nto `update`.\n\nAny place in your `elm-pages` app where the framework lets you pass in a value of type `BackendTask` is a place where you can give `elm-pages` a BackendTask to perform (for example, `Site.head` where you define global head tags for your site).\n\n\n## Basics\n\n@docs BackendTask\n\n@docs map, succeed, fail\n\n@docs fromResult\n\n\n## Chaining Requests\n\n@docs andThen, and, resolve, combine\n\n@docs andMap\n\n@docs map2, map3, map4, map5, map6, map7, map8, map9\n\n\n## FatalError Handling\n\n@docs allowFatal, mapError, onError, toResult\n\n@docs finally\n\n\n## Scripting\n\n@docs do, doEach, sequence, failIf\n\n\n## BackendTask Context\n\nYou can set the following context for a `BackendTask`:\n\n  - `inDir` - Set the working directory\n  - `quiet` - Silence the output\n  - `withEnv` - Set an environment variable\n\nIt's important to understand that a `BackendTask` does not run until it is passed to the `elm-pages` runtime (it is _not_ run as soon as it is defined,\nlike you may be familiar with in other languages like JavaScript). So you can use these functions to set the context\nof a `BackendTask` at any point before you pass it to and it will be applied when the `BackendTask` is run.\n\n@docs inDir, quiet, withEnv\n\n","unions":[],"aliases":[{"name":"BackendTask","comment":" A BackendTask represents data that will be gathered at build time. Multiple `BackendTask`s can be combined together using the `mapN` functions,\nvery similar to how you can manipulate values with Json Decoders in Elm.\n","args":["error","value"],"type":"Pages.StaticHttpRequest.RawRequest error value"}],"values":[{"name":"allowFatal","comment":" Ignore any recoverable error data and propagate the `FatalError`. Similar to a `Cmd` in The Elm Architecture,\na `FatalError` will not do anything except if it is returned at the top-level of your application. Read more\nin the [`FatalError` docs](FatalError).\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> BackendTask.BackendTask FatalError.FatalError data"},{"name":"and","comment":" Chain two `BackendTask`s sequentially.\nThe task on the left side of `|>` runs first. If it succeeds, `next` runs.\nThe left-side result is discarded.\n\nAnalogous to `&&` in shell scripts.\n\nUse `andThen` when you need the previous result, `and` when you just need sequencing.\n\n    step1\n        |> BackendTask.and step2\n\n    -- equivalent to: step1 |> BackendTask.andThen (\\_ -> step2)\n    createSnapshot n typesContent\n        |> BackendTask.and (rewriteMigrationImports n)\n        |> BackendTask.and (createMigrationStub n k)\n        |> BackendTask.and (writeSchemaVersion k)\n\n","type":"BackendTask.BackendTask error b -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"andMap","comment":" A helper for combining `BackendTask`s in pipelines.\n","type":"BackendTask.BackendTask error a -> BackendTask.BackendTask error (a -> b) -> BackendTask.BackendTask error b"},{"name":"andThen","comment":" Build off of the response from a previous `BackendTask` request to build a follow-up request. You can use the data\nfrom the previous response to build up the URL, headers, etc. that you send to the subsequent request.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n\n    licenseData : BackendTask FatalError String\n    licenseData =\n        BackendTask.Http.getJson\n            \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.at [ \"license\", \"url\" ] Decode.string)\n            |> BackendTask.andThen\n                (\\licenseUrl ->\n                    BackendTask.Http.getJson licenseUrl (Decode.field \"description\" Decode.string)\n                )\n            |> BackendTask.allowFatal\n\n","type":"(a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"combine","comment":" Turn a list of `BackendTask`s into a single one.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n\n    type alias Pokemon =\n        { name : String\n        , sprite : String\n        }\n\n    pokemonDetailRequest : BackendTask FatalError (List Pokemon)\n    pokemonDetailRequest =\n        BackendTask.Http.getJson\n            \"https://pokeapi.co/api/v2/pokemon/?limit=3\"\n            (Decode.field \"results\"\n                (Decode.list\n                    (Decode.map2 Tuple.pair\n                        (Decode.field \"name\" Decode.string)\n                        (Decode.field \"url\" Decode.string)\n                        |> Decode.map\n                            (\\( name, url ) ->\n                                BackendTask.Http.getJson url\n                                    (Decode.at\n                                        [ \"sprites\", \"front_default\" ]\n                                        Decode.string\n                                        |> Decode.map (Pokemon name)\n                                    )\n                            )\n                    )\n                )\n            )\n            |> BackendTask.andThen BackendTask.combine\n            |> BackendTask.allowFatal\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"do","comment":" Ignore the resulting value of the BackendTask.\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error ()"},{"name":"doEach","comment":" Perform a List of `BackendTask`s with no output, one-by-one sequentially.\n\nSame as [`sequence`](#sequence), except it ignores the resulting value of each `BackendTask`.\n\n","type":"List.List (BackendTask.BackendTask error ()) -> BackendTask.BackendTask error ()"},{"name":"fail","comment":" ","type":"error -> BackendTask.BackendTask error a"},{"name":"failIf","comment":" If the condition is true, fail with the given `FatalError`. Otherwise, succeed with `()`.\n","type":"Basics.Bool -> FatalError.FatalError -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"finally","comment":" Run a cleanup task after the main task completes, regardless of whether the main task succeeded or failed.\nIf cleanup fails, its error is propagated.\n\nWhen both the main task and cleanup fail, the cleanup error takes precedence (matching `try/finally` behavior in many languages).\n\nThis is useful for resource cleanup, similar to `try/finally` in other languages or `trap cleanup EXIT` in bash.\n\n    import BackendTask exposing (BackendTask)\n    import Pages.Script as Script\n\n    Script.makeTempDirectory \"build-\"\n        |> BackendTask.andThen\n            (\\tmpDir ->\n                doWork tmpDir\n                    |> BackendTask.finally\n                        (Script.removeDirectory { recursive = True } tmpDir)\n            )\n\n","type":"BackendTask.BackendTask error () -> BackendTask.BackendTask error a -> BackendTask.BackendTask error a"},{"name":"fromResult","comment":" Turn `Ok` into `BackendTask.succeed` and `Err` into `BackendTask.fail`.\n","type":"Result.Result error value -> BackendTask.BackendTask error value"},{"name":"inDir","comment":" `inDir` sets the working directory for a `BackendTask`. The working directory of a `BackendTask` will be used to resolve relative paths\nand is relevant for the following types of `BackendTask`s:\n\n  - Reading files ([`BackendTask.File`](BackendTask-File))\n  - Running glob patterns ([`BackendTask.Glob`](BackendTask-Glob))\n  - Executing shell commands ([`BackendTask.Stream.command`](BackendTask-Stream#command)) and [`Pages.Script.sh`](Pages-Script#command)\n\nSee the BackendTask Context section for more about how setting context works.\n\nFor example, these two values will produce the same result:\n\n    import BackendTask.Glob as Glob\n\n    example1 : BackendTask error (List String)\n    example1 =\n        Glob.fromString \"src/**/*.elm\"\n\n    example2 : BackendTask error (List String)\n    example2 =\n        BackendTask.inDir \"src\" (Glob.fromString \"**/*.elm\")\n\nYou can also nest the working directory by using `inDir` multiple times:\n\n    import BackendTask.Glob as Glob\n\n    example3 : BackendTask error (List String)\n    example3 =\n        BackendTask.map2\n            (\\routeModules specialModules ->\n                { routeModules = routeModules\n                , specialModules = specialModules\n                }\n            )\n            (BackendTask.inDir \"Route\" (Glob.fromString \"**/*.elm\"))\n            (Glob.fromString \"*.elm\")\n            |> BackendTask.inDir \"app\"\n\nThe above example will list out files from `app/Route/**/*.elm` and `app/*.elm` because `inDir \"app\"` is applied to the `BackendTask.map2` which combines together both of the Glob tasks.\n\n`inDir` supports absolute paths. In this example, we apply a relative path on top of an absolute path, so the relative path will be resolved relative to the absolute path.\n\n    import BackendTask.Glob as Glob\n\n    example3 : BackendTask error (List String)\n    example3 =\n        BackendTask.map2\n            (\\routeModules specialModules ->\n                { routeModules = routeModules\n                , specialModules = specialModules\n                }\n            )\n            -- same as `Glob.fromString \"/projects/my-elm-pages-blog/app/Route/**/*.elm\"`\n            (BackendTask.inDir \"Route\" (Glob.fromString \"**/*.elm\"))\n            (Glob.fromString \"*.elm\")\n            |> BackendTask.inDir \"/projects/my-elm-pages-blog/app\"\n\nYou can also use `BackendTask.inDir \"..\"` to go up a directory, or \\`BackendTask.inDir \"../..\" to go up two directories, etc.\n\nUse of trailing slashes is optional and does not change the behavior of `inDir`. Leading slashes distinguish between\nabsolute and relative paths, so `BackendTask.inDir \"./src\"` is exactly the same as `BackendTask.inDir \"src\"`, etc.\n\nEach level of nesting with `inDir` will resolve using [NodeJS's `path.resolve`](https://nodejs.org/api/path.html#pathresolvepaths).\n\n","type":"String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"map","comment":" Transform a request into an arbitrary value. The same underlying task will be performed,\nbut mapping allows you to change the resulting values by applying functions to the results.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n\n    starsMessage : BackendTask FatalError String\n    starsMessage =\n        BackendTask.Http.getJson\n            \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.field \"stargazers_count\" Decode.int)\n            |> BackendTask.map\n                (\\stars -> \"⭐️ \" ++ String.fromInt stars)\n            |> BackendTask.allowFatal\n\n","type":"(a -> b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"map2","comment":" Like map, but it takes in two `BackendTask`s.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Env as Env\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n\n    type alias Data =\n        { pokemon : List String, envValue : Maybe String }\n\n    data : BackendTask FatalError Data\n    data =\n        BackendTask.map2 Data\n            (BackendTask.Http.getJson\n                \"https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0\"\n                (Decode.field \"results\"\n                    (Decode.list (Decode.field \"name\" Decode.string))\n                )\n                |> BackendTask.allowFatal\n            )\n            (Env.get \"HELLO\")\n\n","type":"(a -> b -> c) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b -> BackendTask.BackendTask error c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error value9 -> BackendTask.BackendTask error valueCombined"},{"name":"mapError","comment":" ","type":"(error -> errorMapped) -> BackendTask.BackendTask error value -> BackendTask.BackendTask errorMapped value"},{"name":"onError","comment":" ","type":"(error -> BackendTask.BackendTask mappedError value) -> BackendTask.BackendTask error value -> BackendTask.BackendTask mappedError value"},{"name":"quiet","comment":" Sets the verbosity level to `quiet` in the context of the given `BackendTask` (including all nested `BackendTask`s and continuations within it).\n\nThis will turn off performance timing logs. It will also prevent shell commands from printing their output to the console when they are run\n(see [`BackendTask.Stream.command`](BackendTask-Stream#command)).\n\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"resolve","comment":" Helper to remove an inner layer of Request wrapping.\n","type":"BackendTask.BackendTask error (List.List (BackendTask.BackendTask error value)) -> BackendTask.BackendTask error (List.List value)"},{"name":"sequence","comment":" Perform a List of `BackendTask`s one-by-one sequentially. [`combine`](#combine) will perform them all in parallel, which is\ntypically a better default when you aren't sure which you want.\n\nSame as [`doEach`](#doEach), except it ignores the resulting value of each `BackendTask`.\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"succeed","comment":" This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any BackendTask data.\n\n    import BackendTask exposing (BackendTask)\n\n    type alias RouteParams =\n        { name : String }\n\n    pages : BackendTask error (List RouteParams)\n    pages =\n        BackendTask.succeed [ { name = \"elm-pages\" } ]\n\n","type":"a -> BackendTask.BackendTask error a"},{"name":"toResult","comment":" ","type":"BackendTask.BackendTask error data -> BackendTask.BackendTask noError (Result.Result error data)"},{"name":"withEnv","comment":" ","type":"String.String -> String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"}],"binops":[]},{"name":"BackendTask.Custom","comment":" In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.\n\nWith `BackendTask.Custom`, you send and receive JSON to JavaScript running in NodeJS. As with any `BackendTask`, Custom BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See [`BackendTask`](BackendTask) for more about the\nlifecycle of `BackendTask`'s.\n\nThis means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.\n\nA `BackendTask.Custom` will call an async JavaScript function with the given name from the definition in a file called `custom-backend-task.js` in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.\n\n@docs run\n\nHere is the Elm code and corresponding JavaScript definition for getting an environment variable (or an `FatalError BackendTask.Custom.Error` if it isn't found). In this example,\nwe're using `BackendTask.allowFatal` to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the `FatalError` (see [`FatalError`](FatalError)).\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Custom\n    import Json.Encode\n    import OptimizedDecoder as Decode\n\n    data : BackendTask FatalError String\n    data =\n        BackendTask.Custom.run \"environmentVariable\"\n            (Json.Encode.string \"EDITOR\")\n            Decode.string\n            |> BackendTask.allowFatal\n\n    -- will resolve to \"VIM\" if you run `EDITOR=vim elm-pages dev`\n\n```javascript\n// custom-backend-task.js\n\n/**\n* @param { string } fromElm\n* @returns { Promise<string> }\n*/\nexport async function environmentVariable(name) {\n    const result = process.env[name];\n    if (result) {\n      return result;\n    } else {\n      throw `No environment variable called ${name}\n\nAvailable:\n\n${Object.keys(process.env).join(\"\\n\")}\n`;\n    }\n}\n```\n\n\n## Context Parameter\n\nIf you define a second parameter in an exported `custom-backend-task` file function, you can access the `context` object. This object is a JSON object that contains the following fields:\n\n  - `cwd` - the current working directory for the `BackendTask`, set by calls to [`BackendTask.inDir`](BackendTask#inDir). If you don't use `BackendTask.inDir`, this will be the directory from which you are invoking `elm-pages`.\n  - `env` - the environment variables for the `BackendTask`, set by calls to [`BackendTask.withEnv`](BackendTask#withEnv)\n  - `quiet` - a boolean that is `true` if the `BackendTask` is running in quiet mode, set by calls to [`BackendTask.quiet`](BackendTask#quiet)\n\nIf your `BackendTask.Custom` implementation depends on relative file paths, `process.env`, or has logging, it is recommended to use the `context.cwd` and `context.env` fields to ensure\nthat the behavior of your `BackendTask.Custom` is consistent with the core `BackendTask` definitions provided by the framework. For example, the [`BackendTask.Glob`](BackendTask-Glob)\nAPI will resolve glob patterns relative to the `cwd` context.\n\n```js\nimport toml from 'toml';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\n\nexport async function readTomlFile(relativeFilePath, context) {\n  const filePath = path.resolve(context.cwd, relativeFilePath);\n  // toml.parse returns a JSON representation of the TOML input\n  return toml.parse(fs.readFile(filePath));\n}\n```\n\n        import BackendTask exposing (BackendTask)\n        import BackendTask.Custom\n        import Json.Encode\n        import OptimizedDecoder as Decode\n\n        data : BackendTask FatalError String\n        data =\n            BackendTask.Custom.run \"parseTomlFile\"\n                (Json.Encode.string \"my-file.toml\")\n                myJsonDecoder\n                |> BackendTask.allowFatal\n\n\n## Performance\n\nAs with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server. `elm-pages` performs all `BackendTask`'s in parallel whenever possible.\nSo if you do `BackendTask.map2 Tuple.pair myHttpBackendTask myCustomBackendTask`, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use `BackendTask.andThen`,\nit will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your Custom BackendTask definitions.\n\n\n## Error Handling\n\nThere are a few different things that can go wrong when running a custom-backend-task. These possible errors are captured in the `BackendTask.Custom.Error` type.\n\n@docs Error\n\nAny time you throw a JavaScript exception from a BackendTask.Custom definition, it will give you a `CustomBackendTaskException`. It's usually easier to add a `try`/`catch` in your JavaScript code in `custom-backend-task.js`\nto handle possible errors, but you can throw a JSON value and handle it in Elm in the `CustomBackendTaskException` call error.\n\n\n## Decoding JS Date Objects\n\nThese decoders are for use with decoding JS values of type `Date`. If you have control over the format, it may be better to\nbe more explicit with a [Rata Die](https://en.wikipedia.org/wiki/Rata_Die) number value or an ISO-8601 formatted date string instead.\nBut often JavaScript libraries and core APIs will give you JS Date objects, so this can be useful for working with those.\n\n@docs timeDecoder, dateDecoder\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["Error",[]],["ErrorInCustomBackendTaskFile",[]],["MissingCustomBackendTaskFile",[]],["CustomBackendTaskNotDefined",["{ name : String.String }"]],["CustomBackendTaskException",["Json.Decode.Value"]],["NonJsonException",["String.String"]],["ExportIsNotFunction",[]],["DecodeError",["Json.Decode.Error"]]]}],"aliases":[],"values":[{"name":"dateDecoder","comment":" The same as `timeDecoder`, but it converts the decoded `Time.Posix` value into a `Date` with `Date.fromPosix Time.utc`.\n\nJavaScript `Date` objects don't distinguish between values with only a date vs. values with both a date and a time. So be sure\nto use this decoder when you know the semantics represent a date with no associated time (or you're sure you don't care about the time).\n\n","type":"Json.Decode.Decoder Date.Date"},{"name":"run","comment":" ","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Custom.Error } b"},{"name":"timeDecoder","comment":" ","type":"Json.Decode.Decoder Time.Posix"}],"binops":[]},{"name":"BackendTask.Do","comment":"\n\n\n## **This is an optional and experimental module.** It is for doing a continuation style with your [`BackendTask`s](BackendTask).\n\nNote that in order for this style to be usable, you'll need to use a special formatting script that allows you to use\ncontinuation style syntax without indenting each level in the continuation.\n\n\n## Custom Formatting Script\n\nIt is a bit advanced and cumbersome, so beware before committing to this style. That said, here is a script you can use to\napply continuation-style formatting to your Elm code:\n\n<https://gist.github.com/xarvh/1b65cb00e7240f1ccfa0bdbf30f97c62>\n\nYou can see more discussion of continuation style in Elm in this Discourse post: <https://discourse.elm-lang.org/t/experimental-json-decoding-api/2121>.\n\n@docs do\n@docs allowFatal\n\n\n## Defining Your Own Continuation Utilities\n\n`do` is also helpful for building your own continuation-style utilities. For example, here is how [`glob`](#glob) is defined:\n\n    glob : String -> (List String -> BackendTask FatalError a) -> BackendTask FatalError a\n    glob pattern =\n        do <| Glob.fromString pattern\n\nTo define helpers that have no resulting value, it is still useful to have an argument of `()` to allow the code formatter to\nrecognize it as a continuation chain.\n\n    sh :\n        String\n        -> List String\n        -> (() -> BackendTask FatalError b)\n        -> BackendTask FatalError b\n    sh command args =\n        do <| Shell.sh command args\n\n@docs noop\n\n\n## Shell Commands\n\n@docs exec, command\n\n\n## Common Utilities\n\n@docs glob, log, env\n\n@docs each, failIf\n\n","unions":[],"aliases":[],"values":[{"name":"allowFatal","comment":" Same as [`do`](#do), but with a shorthand to call `BackendTask.allowFatal` on it.\n\n    import BackendTask exposing (BackendTask)\n    import FatalError exposing (FatalError)\n    import BackendTask.File as BackendTask.File\n    import BackendTask.Do exposing (allowFatal, do)\n\n    example : BackendTask FatalError ()\n    example =\n        do (BackendTask.File.rawFile \"post-1.md\" |> BackendTask.allowFatal) <|\n            \\post1 ->\n                allowFatal (BackendTask.File.rawFile \"post-2.md\") <|\n                    \\post2 ->\n                        Script.log (post1 ++ \"\\n\\n\" ++ post2)\n\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> (data -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"command","comment":" A do-style helper for [`Script.command`](Pages-Script#command).\n","type":"String.String -> List.List String.String -> (String.String -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"do","comment":" Use any `BackendTask` into a continuation-style task.\n\n    example : BackendTask FatalError ()\n    example =\n        do\n            (Script.question \"What is your name? \")\n        <|\n            \\name ->\n                \\() ->\n                    Script.log (\"Hello \" ++ name ++ \"!\")\n\n","type":"BackendTask.BackendTask error a -> (a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error b"},{"name":"each","comment":"\n\n    checkCompilationInDir : String -> BackendTask FatalError ()\n    checkCompilationInDir dir =\n        glob (dir ++ \"/**/*.elm\") <|\n            \\elmFiles ->\n                each elmFiles\n                    (\\elmFile ->\n                        Shell.sh \"elm\" [ \"make\", elmFile, \"--output\", \"/dev/null\" ]\n                            |> BackendTask.quiet\n                    )\n                <|\n                    \\_ ->\n                        noop\n\n","type":"List.List a -> (a -> BackendTask.BackendTask error b) -> (List.List b -> BackendTask.BackendTask error c) -> BackendTask.BackendTask error c"},{"name":"env","comment":" A do-style helper for [`Env.expect`](BackendTask-Env#expect).\n\n    example : BackendTask FatalError ()\n    example =\n        env \"API_KEY\" <|\n            \\apiKey ->\n                allowFatal (apiRequest apiKey) <|\n                    \\() ->\n                        noop\n\n","type":"String.String -> (String.String -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"exec","comment":" A do-style helper for [`Script.exec`](Pages-Script#exec).\n","type":"String.String -> List.List String.String -> (() -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"failIf","comment":" A do-style helper for [`BackendTask.failIf`](BackendTask#failIf).\n","type":"Basics.Bool -> FatalError.FatalError -> (() -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"glob","comment":" A continuation-style helper for [`Glob.fromString`](BackendTask-Glob#fromString).\n\nIn a shell script, you can think of this as a stand-in for globbing files directly within a command. The [`BackendTask.Stream.command`](BackendTask-Stream#command)\nwhich lets you run shell commands sanitizes and escapes all arguments passed, and does not do glob expansion, so this is helpful for translating\na shell script to Elm.\n\nThis example passes a list of matching file paths along to an `rm -f` command.\n\n    example : BackendTask FatalError ()\n    example =\n        glob \"src/**/*.elm\" <|\n            \\elmFiles ->\n                log (\"Going to delete \" ++ String.fromInt (List.length elmFiles) ++ \" Elm files\") <|\n                    \\() ->\n                        exec \"rm\" (\"-f\" :: elmFiles) <|\n                            \\() ->\n                                noop\n\n","type":"String.String -> (List.List String.String -> BackendTask.BackendTask FatalError.FatalError a) -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"log","comment":" A do-style helper for [`Script.log`](Pages-Script#log).\n\n    example : BackendTask FatalError ()\n    example =\n        log \"Starting script...\" <|\n            \\() ->\n                -- ...\n                log \"Done!\" <|\n                    \\() ->\n                        noop\n\n","type":"String.String -> (() -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error b"},{"name":"noop","comment":" A `BackendTask` that does nothing. Defined as `BackendTask.succeed ()`.\n\nIt's a useful shorthand for when you want to end a continuation chain.\n\n    example : BackendTask FatalError ()\n    example =\n        exec \"ls\" [ \"-l\" ] <|\n            \\() ->\n                log \"Hello, world!\" <|\n                    \\() ->\n                        noop\n\n","type":"BackendTask.BackendTask error ()"}],"binops":[]},{"name":"BackendTask.Env","comment":" Because BackendTask's in `elm-pages` never run in the browser (see [the BackendTask docs](BackendTask)), you can access environment variables securely. As long as the environment variable isn't sent\ndown into the final `Data` value, it won't end up in the client!\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Env\n    import FatalError exposing (FatalError)\n\n    type alias EnvVariables =\n        { sendGridKey : String\n        , siteUrl : String\n        }\n\n    sendEmail : Email -> BackendTask FatalError ()\n    sendEmail email =\n        BackendTask.map2 EnvVariables\n            (BackendTask.Env.expect \"SEND_GRID_KEY\" |> BackendTask.allowFatal)\n            (BackendTask.Env.get \"BASE_URL\"\n                |> BackendTask.map (Maybe.withDefault \"http://localhost:1234\")\n            )\n            |> BackendTask.andThen (sendEmailBackendTask email)\n\n    sendEmailBackendTask : Email -> EnvVariables -> BackendTask FatalError ()\n    sendEmailBackendTask email envVariables =\n        Debug.todo \"Not defined here\"\n\n@docs get, expect\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["MissingEnvVariable",["String.String"]]]}],"aliases":[],"values":[{"name":"expect","comment":" Get an environment variable, or a BackendTask FatalError if there is no environment variable matching that name.\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Env.Error } String.String"},{"name":"get","comment":" Get an environment variable, or Nothing if there is no environment variable matching that name. This `BackendTask`\nwill never fail, but instead will return `Nothing` if the environment variable is missing.\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.File","comment":" This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory), or\nyou can pass in absolute paths beginning with a `/`.\n\n\n## Files With Frontmatter\n\nFrontmatter is a convention used to keep metadata at the top of a file between `---`'s.\n\nFor example, you might have a file called `blog/hello-world.md` with this content:\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\nThe frontmatter is in the [YAML format](https://en.wikipedia.org/wiki/YAML) here. You can also use JSON in your elm-pages frontmatter.\n\n```markdown\n---\n{\"title\": \"Hello, World!\", \"tags\": \"elm\"}\n---\nHey there! This is my first post :)\n```\n\nWhether it's YAML or JSON, you use an `Decode` to decode your frontmatter, so it feels just like using\nplain old JSON in Elm.\n\n@docs bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter\n\n\n## Reading Files Without Frontmatter\n\n@docs jsonFile, rawFile, binaryFile\n\n\n## Checking Files\n\n@docs exists, optional\n\n\n## FatalErrors\n\n@docs FileReadError\n\n","unions":[{"name":"FileReadError","comment":" ","args":["decoding"],"cases":[["FileDoesntExist",[]],["FileReadError",["String.String"]],["DecodingError",["decoding"]]]}],"aliases":[],"values":[{"name":"binaryFile","comment":" Get the raw file content as `Bytes`.\n\nYou could read a file called `hello.jpg` in your root project directory like this:\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import Bytes exposing (Bytes)\n\n    elmBinaryFile : BackendTask Bytes\n    elmBinaryFile =\n        File.binaryFile \"hello.jpg\"\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } Bytes.Bytes"},{"name":"bodyWithFrontmatter","comment":"\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode exposing (Decoder)\n\n    blogPost : BackendTask FatalError BlogPostMetadata\n    blogPost =\n        File.bodyWithFrontmatter blogPostDecoder\n            \"blog/hello-world.md\"\n            |> BackendTask.allowFatal\n\n    type alias BlogPostMetadata =\n        { body : String\n        , title : String\n        , tags : List String\n        }\n\n    blogPostDecoder : String -> Decoder BlogPostMetadata\n    blogPostDecoder body =\n        Decode.map2 (BlogPostMetadata body)\n            (Decode.field \"title\" Decode.string)\n            (Decode.field \"tags\" tagsDecoder)\n\n    tagsDecoder : Decoder (List String)\n    tagsDecoder =\n        Decode.map (String.split \" \")\n            Decode.string\n\nThis will give us a BackendTask that results in the following value:\n\n    value =\n        { body = \"Hey there! This is my first post :)\"\n        , title = \"Hello, World!\"\n        , tags = [ \"elm\" ]\n        }\n\nIt's common to parse the body with a markdown parser or other format.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n    import Html exposing (Html)\n    import Json.Decode as Decode\n\n    example :\n        BackendTask\n            FatalError\n            { title : String\n            , body : List (Html msg)\n            }\n    example =\n        File.bodyWithFrontmatter\n            (\\markdownString ->\n                Decode.map2\n                    (\\title renderedMarkdown ->\n                        { title = title\n                        , body = renderedMarkdown\n                        }\n                    )\n                    (Decode.field \"title\" Decode.string)\n                    (markdownString\n                        |> markdownToView\n                        |> Decode.fromResult\n                    )\n            )\n            \"foo.md\"\n            |> BackendTask.allowFatal\n\n    markdownToView :\n        String\n        -> Result String (List (Html msg))\n    markdownToView markdownString =\n        markdownString\n            |> Markdown.Parser.parse\n            |> Result.mapError (\\_ -> \"Markdown error.\")\n            |> Result.andThen\n                (\\blocks ->\n                    Markdown.Renderer.render\n                        Markdown.Renderer.defaultHtmlRenderer\n                        blocks\n                )\n\n","type":"(String.String -> Json.Decode.Decoder frontmatter) -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"bodyWithoutFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the frontmatter.\n\nFor example, if you have a file called `blog/hello-world.md` with\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n\n    data : BackendTask FatalError String\n    data =\n        File.bodyWithoutFrontmatter \"blog/hello-world.md\"\n            |> BackendTask.allowFatal\n\nThen data will yield the value `\"Hey there! This is my first post :)\"`.\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"},{"name":"exists","comment":" Check if a file or directory exists at the given path. Never fails; returns `True` if the path exists, `False` otherwise.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n\n    checkConfig : BackendTask error Bool\n    checkConfig =\n        File.exists \"config.json\"\n\n","type":"String.String -> BackendTask.BackendTask error Basics.Bool"},{"name":"jsonFile","comment":" Read a file as JSON.\n\nThe Decode will strip off any unused JSON data.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n\n    sourceDirectories : BackendTask FatalError (List String)\n    sourceDirectories =\n        File.jsonFile\n            (Decode.field\n                \"source-directories\"\n                (Decode.list Decode.string)\n            )\n            \"elm.json\"\n            |> BackendTask.allowFatal\n\n","type":"Json.Decode.Decoder a -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } a"},{"name":"onlyFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the body.\n\nThis is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract\njust the metadata.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode exposing (Decoder)\n\n    blogPost : BackendTask FatalError BlogPostMetadata\n    blogPost =\n        File.onlyFrontmatter\n            blogPostDecoder\n            \"blog/hello-world.md\"\n            |> BackendTask.allowFatal\n\n    type alias BlogPostMetadata =\n        { title : String\n        , tags : List String\n        }\n\n    blogPostDecoder : Decoder BlogPostMetadata\n    blogPostDecoder =\n        Decode.map2 BlogPostMetadata\n            (Decode.field \"title\" Decode.string)\n            (Decode.field \"tags\" (Decode.list Decode.string))\n\nIf you wanted to use this to get this metadata for all blog posts in a folder, you could use\nthe [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import Decode exposing (Decoder)\n\n    blogPostFiles : BackendTask (List String)\n    blogPostFiles =\n        Glob.succeed identity\n            |> Glob.captureFilePath\n            |> Glob.match (Glob.literal \"content/blog/\")\n            |> Glob.match Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\n    allMetadata : BackendTask (List BlogPostMetadata)\n    allMetadata =\n        blogPostFiles\n            |> BackendTask.map\n                (List.map\n                    (File.onlyFrontmatter\n                        blogPostDecoder\n                    )\n                )\n            |> BackendTask.resolve\n\n","type":"Json.Decode.Decoder frontmatter -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"optional","comment":" Convert a file reading `BackendTask` into one that returns `Maybe`. If the file doesn't exist, the result is `Nothing`.\nOther errors (decoding errors, permission errors, etc.) are re-thrown as fatal errors.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n\n    readOptionalConfig : BackendTask FatalError (Maybe String)\n    readOptionalConfig =\n        File.rawFile \"config.txt\"\n            |> File.optional\n\n","type":"BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } a -> BackendTask.BackendTask FatalError.FatalError (Maybe.Maybe a)"},{"name":"rawFile","comment":" Get the raw file content. Unlike the frontmatter helpers in this module, this function will not strip off frontmatter if there is any.\n\nThis is the function you want if you are reading in a file directly. For example, if you read in a CSV file, a raw text file, or any other file that doesn't\nhave frontmatter.\n\nThere's a special function for reading in JSON files, [`jsonFile`](#jsonFile). If you're reading a JSON file then be sure to\nuse `jsonFile` to get the benefits of the `Decode` here.\n\nYou could read a file called `hello.txt` in your root project directory like this:\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.File as File\n    import FatalError exposing (FatalError)\n\n    elmJsonFile : BackendTask FatalError String\n    elmJsonFile =\n        File.rawFile \"hello.txt\"\n            |> BackendTask.allowFatal\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"}],"binops":[]},{"name":"BackendTask.Glob","comment":"\n\n@docs Glob\n\nThis module helps you get a List of matching file paths from your local file system as a [`BackendTask`](BackendTask#BackendTask). See the [`BackendTask`](BackendTask) module documentation\nfor ways you can combine and map `BackendTask`s.\n\nA common example would be to find all the markdown files of your blog posts. If you have all your blog posts in `content/blog/*.md`\n, then you could use that glob pattern in most shells to refer to each of those files.\n\nWith the `BackendTask.Glob` API, you could get all of those files like so:\n\n    import BackendTask exposing (BackendTask)\n\n    blogPostsGlob : BackendTask error (List String)\n    blogPostsGlob =\n        Glob.succeed (\\slug -> slug)\n            |> Glob.match (Glob.literal \"content/blog/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nLet's say you have these files locally:\n\n```shell\n- elm.json\n- src/\n- content/\n  - blog/\n    - first-post.md\n    - second-post.md\n```\n\nWe would end up with a `BackendTask` like this:\n\n    BackendTask.succeed [ \"first-post\", \"second-post\" ]\n\nOf course, if you add or remove matching files, the BackendTask will get those new files (unlike `BackendTask.succeed`). That's why we have Glob!\n\nYou can even see the `elm-pages dev` server will automatically flow through any added/removed matching files with its hot module reloading.\n\nBut why did we get `\"first-post\"` instead of a full file path, like `\"content/blog/first-post.md\"`? That's the difference between\n`capture` and `match`.\n\n\n## Capture and Match\n\nThere are two functions for building up a Glob pattern: `capture` and `match`.\n\n`capture` and `match` both build up a `Glob` pattern that will match 0 or more files on your local file system.\nThere will be one argument for every `capture` in your pipeline, whereas `match` does not apply any arguments.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    blogPostsGlob : BackendTask error (List String)\n    blogPostsGlob =\n        Glob.succeed (\\slug -> slug)\n            -- no argument from this, but we will only\n            -- match files that begin with `content/blog/`\n            |> Glob.match (Glob.literal \"content/blog/\")\n            -- we get the value of the `wildcard`\n            -- as the slug argument\n            |> Glob.capture Glob.wildcard\n            -- no argument from this, but we will only\n            -- match files that end with `.md`\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nSo to understand _which_ files will match, you can ignore whether you are using `capture` or `match` and just read\nthe patterns you're using in order to understand what will match. To understand what Elm data type you will get\n_for each matching file_, you need to see which parts are being captured and how each of those captured values are being\nused in the function you use in `Glob.succeed`.\n\n@docs capture, match\n\n`capture` is a lot like building up a JSON decoder with a pipeline.\n\nLet's try our blogPostsGlob from before, but change every `match` to `capture`.\n\n    import BackendTask exposing (BackendTask)\n\n    blogPostsGlob :\n        BackendTask\n            error\n            (List\n                { filePath : String\n                , slug : String\n                }\n            )\n    blogPostsGlob =\n        Glob.succeed\n            (\\capture1 capture2 capture3 ->\n                { filePath = capture1 ++ capture2 ++ capture3\n                , slug = capture2\n                }\n            )\n            |> Glob.capture (Glob.literal \"content/blog/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.capture (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nNotice that we now need 3 arguments at the start of our pipeline instead of 1. That's because\nwe apply 1 more argument every time we do a `Glob.capture`, much like `Json.Decode.Pipeline.required`, or other pipeline APIs.\n\nNow we actually have the full file path of our files. But having that slug (like `first-post`) is also very helpful sometimes, so\nwe kept that in our record as well. So we'll now have the equivalent of this `BackendTask` with the current `.md` files in our `blog` folder:\n\n    BackendTask.succeed\n        [ { filePath = \"content/blog/first-post.md\"\n          , slug = \"first-post\"\n          }\n        , { filePath = \"content/blog/second-post.md\"\n          , slug = \"second-post\"\n          }\n        ]\n\nHaving the full file path lets us read in files. But concatenating it manually is tedious\nand error prone. That's what the [`captureFilePath`](#captureFilePath) helper is for.\n\n\n## Reading matching files\n\nYou can use the less powerful but more familiar and terse `fromString` helpers if you only need to find matching file paths\n(but don't care about parsing out parts of the paths). This is helpful when you need a reference to matching files and\nyou are only using the file paths to then read or do scripting tasks with those paths.\n\n@docs fromString, fromStringWithOptions\n\n@docs captureFilePath\n\nIn many cases you will want to take the matching files from a `Glob` and then read the body or frontmatter from matching files.\n\n\n## Reading Metadata for each Glob Match\n\nFor example, if we had files like this:\n\n```markdown\n---\ntitle: My First Post\n---\nThis is my first post!\n```\n\nThen we could read that title for our blog post list page using our `blogPosts` `BackendTask` that we defined above.\n\n    import BackendTask.File\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode exposing (Decoder)\n\n    titles : BackendTask FatalError (List BlogPost)\n    titles =\n        blogPosts\n            |> BackendTask.map\n                (List.map\n                    (\\blogPost ->\n                        BackendTask.File.onlyFrontmatter\n                            blogFrontmatterDecoder\n                            blogPost.filePath\n                    )\n                )\n            |> BackendTask.resolve\n            |> BackendTask.allowFatal\n\n    type alias BlogPost =\n        { title : String }\n\n    blogFrontmatterDecoder : Decoder BlogPost\n    blogFrontmatterDecoder =\n        Decode.map BlogPost\n            (Decode.field \"title\" Decode.string)\n\nThat will give us\n\n    BackendTask.succeed\n        [ { title = \"My First Post\" }\n        , { title = \"My Second Post\" }\n        ]\n\n\n## Capturing Patterns\n\n@docs wildcard, recursiveWildcard\n\n\n## Capturing Specific Characters\n\n@docs int, digits\n\n\n## Capturing File Stats\n\nYou can access a file's stats including timestamps when the file was created and modified, and file size.\n\n@docs FileStats, captureStats\n\n\n## Matching a Specific Number of Files\n\n@docs expectUniqueMatch, expectUniqueMatchFromList\n\n\n## Glob Patterns\n\n@docs literal\n\n@docs map, succeed\n\n@docs oneOf\n\n@docs zeroOrMore, atLeastOne\n\n\n## Getting Glob Data from a BackendTask\n\n@docs toBackendTask\n\n\n### With Custom Options\n\n@docs toBackendTaskWithOptions\n\n@docs defaultOptions, Options, Include\n\n","unions":[{"name":"Include","comment":" <https://github.com/mrmlnc/fast-glob#onlyfiles>\n\n<https://github.com/mrmlnc/fast-glob#onlydirectories>\n\n","args":[],"cases":[["OnlyFiles",[]],["OnlyFolders",[]],["FilesAndFolders",[]]]}],"aliases":[{"name":"FileStats","comment":" The information about a file that you can access when you use [`captureStats`](#captureStats).\n","args":[],"type":"{ fullPath : String.String, sizeInBytes : Basics.Int, lastContentChange : Time.Posix, lastAccess : Time.Posix, lastFileChange : Time.Posix, createdAt : Time.Posix, isDirectory : Basics.Bool }"},{"name":"Glob","comment":" A pattern to match local files and capture parts of the path into a nice Elm data type.\n","args":["a"],"type":"BackendTask.Internal.Glob.Glob a"},{"name":"Options","comment":" Custom options you can pass in to run the glob with [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n\n    { includeDotFiles = Bool -- https://github.com/mrmlnc/fast-glob#dot\n    , include = Include -- return results that are `OnlyFiles`, `OnlyFolders`, or both `FilesAndFolders` (default is `OnlyFiles`)\n    , followSymbolicLinks = Bool -- https://github.com/mrmlnc/fast-glob#followsymboliclinks\n    , caseSensitiveMatch = Bool -- https://github.com/mrmlnc/fast-glob#casesensitivematch\n    , gitignore = Bool -- https://www.npmjs.com/package/globby#gitignore\n    , maxDepth = Maybe Int -- https://github.com/mrmlnc/fast-glob#deep\n    }\n\n","args":[],"type":"{ includeDotFiles : Basics.Bool, include : BackendTask.Glob.Include, followSymbolicLinks : Basics.Bool, caseSensitiveMatch : Basics.Bool, gitignore : Basics.Bool, maxDepth : Maybe.Maybe Basics.Int }"}],"values":[{"name":"atLeastOne","comment":" ","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob ( a, List.List a )"},{"name":"capture","comment":" Adds on to the glob pattern, and captures it in the resulting Elm match value. That means this both changes which\nfiles will match, and gives you the sub-match as Elm data for each matching file.\n\nExactly the same as `match` except it also captures the matched sub-pattern.\n\n    type alias ArchivesArticle =\n        { year : String\n        , month : String\n        , day : String\n        , slug : String\n        }\n\n    archives : BackendTask error ArchivesArticle\n    archives =\n        Glob.succeed ArchivesArticle\n            |> Glob.match (Glob.literal \"archive/\")\n            |> Glob.capture Glob.int\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.int\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.int\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nThe file `archive/1977/06/10/apple-2-released.md` will give us this match:\n\n    matches : List error ArchivesArticle\n    matches =\n        BackendTask.succeed\n            [ { year = 1977\n              , month = 6\n              , day = 10\n              , slug = \"apple-2-released\"\n              }\n            ]\n\nWhen possible, it's best to grab data and turn it into structured Elm data when you have it. That way,\nyou don't end up with duplicate validation logic and data normalization, and your code will be more robust.\n\nIf you only care about getting the full matched file paths, you can use `match`. `capture` is very useful because\nyou can pick apart structured data as you build up your glob pattern. This follows the principle of\n[Parse, Don't Validate](https://elm-radio.com/episode/parse-dont-validate/).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob (a -> value) -> BackendTask.Glob.Glob value"},{"name":"captureFilePath","comment":"\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    blogPosts :\n        BackendTask\n            error\n            (List\n                { filePath : String\n                , slug : String\n                }\n            )\n    blogPosts =\n        Glob.succeed\n            (\\filePath slug ->\n                { filePath = filePath\n                , slug = slug\n                }\n            )\n            |> Glob.captureFilePath\n            |> Glob.match (Glob.literal \"content/blog/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nThis function does not change which files will or will not match. It just gives you the full matching\nfile path in your `Glob` pipeline.\n\nWhenever possible, it's a good idea to use function to make sure you have an accurate file path when you need to read a file.\n\n","type":"BackendTask.Glob.Glob (String.String -> value) -> BackendTask.Glob.Glob value"},{"name":"captureStats","comment":"\n\n    import BackendTask.Glob as Glob\n\n    recentlyChangedRouteModules : BackendTask error (List ( Time.Posix, List String ))\n    recentlyChangedRouteModules =\n        Glob.succeed\n            (\\fileStats directoryName fileName ->\n                ( fileStats.lastContentChange\n                , directoryName ++ [ fileName ]\n                )\n            )\n            |> Glob.captureStats\n            |> Glob.match (Glob.literal \"app/Route/\")\n            |> Glob.capture Glob.recursiveWildcard\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".elm\")\n            |> Glob.toBackendTask\n            |> BackendTask.map\n                (\\entries ->\n                    entries\n                        |> List.sortBy (\\( lastChanged, _ ) -> Time.posixToMillis lastChanged)\n                        |> List.reverse\n                )\n\n","type":"BackendTask.Glob.Glob (BackendTask.Glob.FileStats -> value) -> BackendTask.Glob.Glob value"},{"name":"defaultOptions","comment":" The default options used in [`toBackendTask`](#toBackendTask). To use a custom set of options, use [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n","type":"BackendTask.Glob.Options"},{"name":"digits","comment":" This is similar to [`wildcard`](#wildcard), but it will only match 1 or more digits (i.e. `[0-9]+`).\n\nSee [`int`](#int) for a convenience function to get an Int value instead of a String of digits.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"expectUniqueMatch","comment":" Sometimes you want to make sure there is a unique file matching a particular pattern.\nThis is a simple helper that will give you a `BackendTask` error if there isn't exactly 1 matching file.\nIf there is exactly 1, then you successfully get back that single match.\n\nFor example, maybe you can have\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    findBlogBySlug : String -> BackendTask FatalError String\n    findBlogBySlug slug =\n        Glob.succeed identity\n            |> Glob.captureFilePath\n            |> Glob.match (Glob.literal \"blog/\")\n            |> Glob.match (Glob.literal slug)\n            |> Glob.match\n                (Glob.oneOf\n                    ( ( \"\", () )\n                    , [ ( \"/index\", () ) ]\n                    )\n                )\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.expectUniqueMatch\n            |> BackendTask.allowFatal\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n    - first-post/\n        - index.md\n```\n\nThis would give us:\n\n    results : BackendTask FatalError String\n    results =\n        BackendTask.succeed \"blog/first-post/index.md\"\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n    - first-post.md\n    - first-post/\n        - index.md\n```\n\nThen we will get a `BackendTask` error saying `More than one file matched.` Keep in mind that `BackendTask` failures\nin build-time routes will cause a build failure, giving you the opportunity to fix the problem before users see the issue,\nso it's ideal to make this kind of assertion rather than having fallback behavior that could silently cover up\nissues (like if we had instead ignored the case where there are two or more matching blog post files).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : String.String } a"},{"name":"expectUniqueMatchFromList","comment":" ","type":"List.List (BackendTask.Glob.Glob a) -> BackendTask.BackendTask String.String a"},{"name":"fromString","comment":" Runs a glob string directly, with `include = FilesAndFolders`. Behavior is similar to using glob patterns in a shell.\n\nIf you need to capture specific parts of the path, you can use `capture` and `match` functions instead. `fromString`\nonly allows you to capture a list of matching file paths.\n\nThe following glob syntax is supported:\n\n  - `*` matches any number of characters except for `/`\n  - `**` matches any number of characters including `/`\n\nFor example, if we have the following files:\n\n```shell\n- src/\n    - Main.elm\n    - Ui/\n        - Icon.elm\n- content/\n    - blog/\n        - first-post.md\n        - second-post.md\n```\n\n    import BackendTask.Glob as Glob\n\n    blogPosts : BackendTask error (List String)\n    blogPosts =\n        Glob.fromString \"content/blog/*.md\"\n\n    --> BackendTask.succeed [ \"content/blog/first-post.md\", \"content/blog/second-post.md\" ]\n    elmFiles : BackendTask error (List String)\n    elmFiles =\n        Glob.fromString \"src/**/*.elm\"\n\n    --> BackendTask.succeed [ \"src/Main.elm\", \"src/Ui\", \"src/Ui/Icon.elm\" ]\n\n","type":"String.String -> BackendTask.BackendTask error (List.List String.String)"},{"name":"fromStringWithOptions","comment":" Same as [`fromString`](#fromString), but with custom [`Options`](#Options).\n","type":"BackendTask.Glob.Options -> String.String -> BackendTask.BackendTask error (List.List String.String)"},{"name":"int","comment":" Same as [`digits`](#digits), but it safely turns the digits String into an `Int`.\n\nLeading 0's are ignored.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    slides : BackendTask error (List Int)\n    slides =\n        Glob.succeed identity\n            |> Glob.match (Glob.literal \"slide-\")\n            |> Glob.capture Glob.int\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nWith files\n\n```shell\n- slide-no-match.md\n- slide-.md\n- slide-1.md\n- slide-01.md\n- slide-2.md\n- slide-03.md\n- slide-4.md\n- slide-05.md\n- slide-06.md\n- slide-007.md\n- slide-08.md\n- slide-09.md\n- slide-10.md\n- slide-11.md\n```\n\nYields\n\n    matches : BackendTask error (List Int)\n    matches =\n        BackendTask.succeed\n            [ 1\n            , 1\n            , 2\n            , 3\n            , 4\n            , 5\n            , 6\n            , 7\n            , 8\n            , 9\n            , 10\n            , 11\n            ]\n\nNote that neither `slide-no-match.md` nor `slide-.md` match.\nAnd both `slide-1.md` and `slide-01.md` match and turn into `1`.\n\n","type":"BackendTask.Glob.Glob Basics.Int"},{"name":"literal","comment":" Match a literal part of a path. Can include `/`s.\n\nSome common uses include\n\n  - The leading part of a pattern, to say \"starts with `content/blog/`\"\n  - The ending part of a pattern, to say \"ends with `.md`\"\n  - In-between wildcards, to say \"these dynamic parts are separated by `/`\"\n\n```elm\nimport BackendTask exposing (BackendTask)\nimport BackendTask.Glob as Glob\n\nblogPosts =\n    Glob.succeed\n        (\\section slug ->\n            { section = section, slug = slug }\n        )\n        |> Glob.match (Glob.literal \"content/blog/\")\n        |> Glob.capture Glob.wildcard\n        |> Glob.match (Glob.literal \"/\")\n        |> Glob.capture Glob.wildcard\n        |> Glob.match (Glob.literal \".md\")\n```\n\n","type":"String.String -> BackendTask.Glob.Glob String.String"},{"name":"map","comment":" A `Glob` can be mapped. This can be useful for transforming a sub-match in-place.\n\nFor example, if you wanted to take the slugs for a blog post and make sure they are normalized to be all lowercase, you\ncould use\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    blogPostsGlob : BackendTask error (List String)\n    blogPostsGlob =\n        Glob.succeed (\\slug -> slug)\n            |> Glob.match (Glob.literal \"content/blog/\")\n            |> Glob.capture (Glob.wildcard |> Glob.map String.toLower)\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nIf you want to validate file formats, you can combine that with some `BackendTask` helpers to turn a `Glob (Result String value)` into\na `BackendTask FatalError (List value)`.\n\nFor example, you could take a date and parse it.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    example : BackendTask FatalError (List ( String, String ))\n    example =\n        Glob.succeed\n            (\\dateResult slug ->\n                dateResult\n                    |> Result.map (\\okDate -> ( okDate, slug ))\n            )\n            |> Glob.match (Glob.literal \"blog/\")\n            |> Glob.capture (Glob.recursiveWildcard |> Glob.map expectDateFormat)\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n            |> BackendTask.map (List.map BackendTask.fromResult)\n            |> BackendTask.resolve\n\n    expectDateFormat : List String -> Result FatalError String\n    expectDateFormat dateParts =\n        case dateParts of\n            [ year, month, date ] ->\n                Ok (String.join \"-\" [ year, month, date ])\n\n            _ ->\n                Err <| FatalError.fromString \"Unexpected date format, expected yyyy/mm/dd folder structure.\"\n\n","type":"(a -> b) -> BackendTask.Glob.Glob a -> BackendTask.Glob.Glob b"},{"name":"match","comment":" Adds on to the glob pattern, but does not capture it in the resulting Elm match value. That means this changes which\nfiles will match, but does not change the Elm data type you get for each matching file.\n\nExactly the same as `capture` except it doesn't capture the matched sub-pattern.\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob value -> BackendTask.Glob.Glob value"},{"name":"oneOf","comment":"\n\n    import BackendTask.Glob as Glob\n\n    type Extension\n        = Json\n        | Yml\n\n    type alias DataFile =\n        { name : String\n        , extension : String\n        }\n\n    dataFiles : BackendTask error (List DataFile)\n    dataFiles =\n        Glob.succeed DataFile\n            |> Glob.match (Glob.literal \"my-data/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".\")\n            |> Glob.capture\n                (Glob.oneOf\n                    ( ( \"yml\", Yml )\n                    , [ ( \"json\", Json )\n                      ]\n                    )\n                )\n\nIf we have the following files\n\n```shell\n- my-data/\n    - authors.yml\n    - events.json\n```\n\nThat gives us\n\n    results : BackendTask error (List DataFile)\n    results =\n        BackendTask.succeed\n            [ { name = \"authors\"\n              , extension = Yml\n              }\n            , { name = \"events\"\n              , extension = Json\n              }\n            ]\n\nYou could also match an optional file path segment using `oneOf`.\n\n    rootFilesMd : BackendTask error (List String)\n    rootFilesMd =\n        Glob.succeed (\\slug -> slug)\n            |> Glob.match (Glob.literal \"blog/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match\n                (Glob.oneOf\n                    ( ( \"\", () )\n                    , [ ( \"/index\", () ) ]\n                    )\n                )\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\nWith these files:\n\n```markdown\n- blog/\n    - first-post.md\n    - second-post/\n        - index.md\n```\n\nThis would give us:\n\n    results : BackendTask error (List String)\n    results =\n        BackendTask.succeed\n            [ \"first-post\"\n            , \"second-post\"\n            ]\n\n","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob a"},{"name":"recursiveWildcard","comment":" Matches any number of characters, including `/`, as long as it's the only thing in a path part.\n\nIn contrast, `wildcard` will never match `/`, so it only matches within a single path part.\n\nThis is the elm-pages equivalent of `**/*.txt` in standard shell syntax:\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    example : BackendTask error (List ( List String, String ))\n    example =\n        Glob.succeed Tuple.pair\n            |> Glob.match (Glob.literal \"articles/\")\n            |> Glob.capture Glob.recursiveWildcard\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".txt\")\n            |> Glob.toBackendTask\n\nWith these files:\n\n```shell\n- articles/\n  - google-io-2021-recap.txt\n  - archive/\n    - 1977/\n      - 06/\n        - 10/\n          - apple-2-announced.txt\n```\n\nWe would get the following matches:\n\n    matches : BackendTask error (List ( List String, String ))\n    matches =\n        BackendTask.succeed\n            [ ( [ \"archive\", \"1977\", \"06\", \"10\" ], \"apple-2-announced\" )\n            , ( [], \"google-io-2021-recap\" )\n            ]\n\nNote that the recursive wildcard conveniently gives us a `List String`, where\neach String is a path part with no slashes (like `archive`).\n\nAnd also note that it matches 0 path parts into an empty list.\n\nIf we didn't include the `wildcard` after the `recursiveWildcard`, then we would only get\na single level of matches because it is followed by a file extension.\n\n    example : BackendTask error (List String)\n    example =\n        Glob.succeed identity\n            |> Glob.match (Glob.literal \"articles/\")\n            |> Glob.capture Glob.recursiveWildcard\n            |> Glob.match (Glob.literal \".txt\")\n\n    matches : BackendTask error (List String)\n    matches =\n        BackendTask.succeed\n            [ \"google-io-2021-recap\"\n            ]\n\nThis is usually not what is intended. Using `recursiveWildcard` is usually followed by a `wildcard` for this reason.\n\n","type":"BackendTask.Glob.Glob (List.List String.String)"},{"name":"succeed","comment":" `succeed` is how you start a pipeline for a `Glob`. You will need one argument for each `capture` in your `Glob`.\n","type":"constructor -> BackendTask.Glob.Glob constructor"},{"name":"toBackendTask","comment":" In order to get match data from your glob, turn it into a `BackendTask` with this function.\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"toBackendTaskWithOptions","comment":" Same as toBackendTask, but lets you set custom glob options. For example, to list folders instead of files,\n\n    import BackendTask.Glob as Glob exposing (OnlyFolders, defaultOptions)\n\n    matchingFiles : Glob a -> BackendTask error (List a)\n    matchingFiles glob =\n        glob\n            |> Glob.toBackendTaskWithOptions { defaultOptions | include = OnlyFolders }\n\n","type":"BackendTask.Glob.Options -> BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"wildcard","comment":" Matches anything except for a `/` in a file path. You may be familiar with this syntax from shells like bash\nwhere you can run commands like `rm client/*.js` to remove all `.js` files in the `client` directory.\n\nJust like a `*` glob pattern in bash, this `Glob.wildcard` function will only match within a path part. If you need to\nmatch 0 or more path parts like, see `recursiveWildcard`.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Glob as Glob\n\n    type alias BlogPost =\n        { year : String\n        , month : String\n        , day : String\n        , slug : String\n        }\n\n    example : BackendTask error (List BlogPost)\n    example =\n        Glob.succeed BlogPost\n            |> Glob.match (Glob.literal \"blog/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \"-\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \"-\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \"/\")\n            |> Glob.capture Glob.wildcard\n            |> Glob.match (Glob.literal \".md\")\n            |> Glob.toBackendTask\n\n```shell\n\n- blog/\n  - 2021-05-27/\n    - first-post.md\n```\n\nThat will match to:\n\n    results : BackendTask error (List BlogPost)\n    results =\n        BackendTask.succeed\n            [ { year = \"2021\"\n              , month = \"05\"\n              , day = \"27\"\n              , slug = \"first-post\"\n              }\n            ]\n\nNote that we can \"destructure\" the date part of this file path in the format `yyyy-mm-dd`. The `wildcard` matches\nwill match _within_ a path part (think between the slashes of a file path). `recursiveWildcard` can match across path parts.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"zeroOrMore","comment":" ","type":"List.List String.String -> BackendTask.Glob.Glob (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.Http","comment":" `BackendTask.Http` requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.\n\nThe key differences are:\n\n  - `BackendTask.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)\n  - `BackendTask.Http.Request`s have a built-in `BackendTask.andThen` that allows you to perform follow-up requests without using tasks\n\n\n## Scenarios where BackendTask.Http is a good fit\n\nIf you need data that is refreshed often you may want to do a traditional HTTP request with the `elm/http` package.\nThe kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never).\nA common pattern is to trigger a new build when data changes. Many JAMstack services\nallow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So\nyou may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added\nor updated on a CMS service like Contentful.\n\nIn scenarios like this, you can serve data that is just as up-to-date as it would be using `elm/http`, but you get the performance\ngains of using `BackendTask.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits\nin [this article introducing BackendTask.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).\n\n\n## Scenarios where BackendTask.Http is not a good fit\n\n  - Data that is specific to the logged-in user\n  - Data that needs to be the very latest and changes often (for example, sports scores)\n\n\n## Making a Request\n\n@docs get, getJson\n\n@docs post\n\n\n## Decoding Request Body\n\n@docs Expect, expectString, expectJson, expectBytes, expectWhatever\n\n\n## Error Handling\n\n@docs Error\n\n\n## General Requests\n\n@docs request\n\n\n## Building a BackendTask.Http Request Body\n\nThe way you build a body is analogous to the `elm/http` package.\n\n@docs Body, emptyBody, stringBody, jsonBody, bytesBody\n\n\n## Multipart Request Bodies\n\n@docs multipartBody, Part, stringPart, bytesPart, bytesPartWithFilename\n\n\n## Caching Options\n\n`elm-pages` performs GET requests using a local HTTP cache by default. These requests are not performed using Elm's `elm/http`,\nbut rather are performed in NodeJS. Under the hood it uses [the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen).\nOnly GET requests made with `get`, `getJson`, or `getWithOptions` use local caching. Requests made with [`BackendTask.Http.request`](#request)\nare not cached, even if the method is set to `GET`.\n\nIn dev mode, assets are cached more aggressively by default, whereas for a production build assets use a default to revalidate each cached response's freshness before using it (the `ForceRevalidate` [`CacheStrategy`](#CacheStrategy)).\n\nThe default caching behavior for GET requests is to use a local cache in `.elm-pages/http-cache`. This uses the same caching behavior\nthat browsers use to avoid re-downloading content when it hasn't changed. Servers can set HTTP response headers to explicitly control\nthis caching behavior.\n\n  - [`cache-control` HTTP response headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) let you set a length of time before considering an asset stale. This could mean that the server considers it acceptable for an asset to be somewhat outdated, or this could mean that the asset is guaranteed to be up-to-date until it is stale - those semantics are up to the server.\n  - `Last-Modified` and `ETag` HTTP response headers can be returned by the server allow [Conditional Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests). Conditional Requests let us send back the `Last-Modified` timestamp or `etag` hash for assets that are in our local cache to the server to check if the asset is fresh, and skip re-downloading it if it is unchanged (or download a fresh one otherwise).\n\nIt's important to note that depending on how the server sets these HTTP response headers, we may have outdated data - either because the server explicitly allows assets to become outdated with their cache-control headers, OR because cache-control headers are not set. When these headers aren't explicitly set, [clients are allowed to cache assets for 10% of the amount of time since it was last modified](https://httpwg.org/specs/rfc7234.html#heuristic.freshness).\nFor production builds, the default caching will ignore both the implicit and explicit information about an asset's freshness and _always_ revalidate it before using a locally cached response.\n\n@docs getWithOptions\n\n@docs CacheStrategy\n\n\n## Including HTTP Metadata\n\n@docs withMetadata, Metadata\n\n","unions":[{"name":"CacheStrategy","comment":" ","args":[],"cases":[["IgnoreCache",[]],["ForceRevalidate",[]],["ForceReload",[]],["ForceCache",[]],["ErrorUnlessCached",[]]]},{"name":"Error","comment":" ","args":[],"cases":[["BadUrl",["String.String"]],["Timeout",[]],["NetworkError",[]],["BadStatus",["BackendTask.Http.Metadata","String.String"]],["BadBody",["Maybe.Maybe Json.Decode.Error","String.String"]]]},{"name":"Expect","comment":" Analogous to the `Expect` type in the `elm/http` package. This represents how you will process the data that comes\nback in your BackendTask.Http request.\n\nYou can derive `ExpectJson` from `ExpectString`. Or you could build your own helper to process the String\nas XML, for example, or give an `elm-pages` build error if the response can't be parsed as XML.\n\n","args":["value"],"cases":[]},{"name":"Part","comment":" A single part of a multipart body.\n","args":[],"cases":[]}],"aliases":[{"name":"Body","comment":" A body for a BackendTask.Http request.\n","args":[],"type":"Pages.Internal.StaticHttpBody.Body"},{"name":"Metadata","comment":" ","args":[],"type":"{ url : String.String, statusCode : Basics.Int, statusText : String.String, headers : Dict.Dict String.String String.String }"}],"values":[{"name":"bytesBody","comment":" Build a body from `Bytes` for a BackendTask.Http request. See [elm/http's `Http.bytesBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#bytesBody).\n","type":"String.String -> Bytes.Bytes -> BackendTask.Http.Body"},{"name":"bytesPart","comment":" A bytes part for a multipart body. Provide a field name, MIME type, and\nbytes content. Useful for file uploads where the file data comes from\n`BackendTask.File` or similar.\n\n    BackendTask.Http.bytesPart \"file\" \"image/png\" imageBytes\n\n","type":"String.String -> String.String -> Bytes.Bytes -> BackendTask.Http.Part"},{"name":"bytesPartWithFilename","comment":" Like `bytesPart`, but includes a filename in the Content-Disposition header.\nMany file upload APIs require or use this.\n\n    BackendTask.Http.bytesPartWithFilename \"file\" \"image/png\" \"photo.png\" imageBytes\n\n","type":"String.String -> String.String -> String.String -> Bytes.Bytes -> BackendTask.Http.Part"},{"name":"emptyBody","comment":" Build an empty body for a BackendTask.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).\n","type":"BackendTask.Http.Body"},{"name":"expectBytes","comment":" ","type":"Bytes.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectJson","comment":" Handle the incoming response as JSON and don't optimize the asset and strip out unused values.\nBe sure to use the `BackendTask.Http.request` function if you want an optimized request that\nstrips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request\nwhere the JSON payload is already trimmed down to the data you explicitly requested.\n\nIf the function you pass to `expectString` yields an `Err`, then you will get a build error that will\nfail your `elm-pages` build and print out the String from the `Err`.\n\n","type":"Json.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectString","comment":" Gives the HTTP response body as a raw String.\n\n    import BackendTask exposing (BackendTask)\n    import BackendTask.Http\n\n    request : BackendTask String\n    request =\n        BackendTask.Http.request\n            { url = \"https://example.com/file.txt\"\n            , method = \"GET\"\n            , headers = []\n            , body = BackendTask.Http.emptyBody\n            }\n            BackendTask.Http.expectString\n\n","type":"BackendTask.Http.Expect String.String"},{"name":"expectWhatever","comment":" ","type":"value -> BackendTask.Http.Expect value"},{"name":"get","comment":" A simplified helper around [`BackendTask.Http.getWithOptions`](#getWithOptions), which builds up a GET request with\nthe default retries, timeout, and HTTP caching options. If you need to configure those options or include HTTP request headers,\nuse the more flexible `getWithOptions`.\n\n    import BackendTask\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n\n    getRequest : BackendTask (FatalError Error) String\n    getRequest =\n        BackendTask.Http.get\n            \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            BackendTask.Http.expectString\n\n","type":"String.String -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getJson","comment":" A simplified helper around [`BackendTask.Http.get`](#get), which builds up a BackendTask.Http GET request with `expectJson`.\n\n    import BackendTask\n    import BackendTask.Http\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode exposing (Decoder)\n\n    getRequest : BackendTask (FatalError Error) Int\n    getRequest =\n        BackendTask.Http.getJson\n            \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.field \"stargazers_count\" Decode.int)\n\n","type":"String.String -> Json.Decode.Decoder a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getWithOptions","comment":" Perform a GET request, with some additional options for the HTTP request, including options for caching behavior.\n\n  - `retries` - Default is 0. Will try performing request again if set to a number greater than 0.\n  - `timeoutInMs` - Default is no timeout.\n  - `cacheStrategy` - The [caching options are passed to the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen#opts-cache)\n  - `cachePath` - override the default directory for the local HTTP cache. This can be helpful if you want more granular control to clear some HTTP caches more or less frequently than others. Or you may want to preserve the local cache for some requests in your build server, but not store the cache for other requests.\n\n","type":"{ url : String.String, expect : BackendTask.Http.Expect a, headers : List.List ( String.String, String.String ), cacheStrategy : Maybe.Maybe BackendTask.Http.CacheStrategy, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int, cachePath : Maybe.Maybe String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"jsonBody","comment":" Builds a JSON body for a BackendTask.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).\n","type":"Json.Encode.Value -> BackendTask.Http.Body"},{"name":"multipartBody","comment":" Create a multipart/form-data body from a list of parts.\n\n    import BackendTask.Http\n    import Bytes.Encode\n\n    BackendTask.Http.post \"https://example.com/upload\"\n        (BackendTask.Http.multipartBody\n            [ BackendTask.Http.stringPart \"title\" \"My Photo\"\n            , BackendTask.Http.bytesPart \"file\" \"image/png\" imageBytes\n            ]\n        )\n        (BackendTask.Http.expectJson decoder)\n\nThe multipart encoding is handled on the Node.js side, which generates\na cryptographically random boundary via `crypto.randomUUID()`. This\navoids any possibility of boundary collisions with part content.\n\n","type":"List.List BackendTask.Http.Part -> BackendTask.Http.Body"},{"name":"post","comment":" ","type":"String.String -> BackendTask.Http.Body -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"request","comment":" ","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"stringBody","comment":" Builds a string body for a BackendTask.Http request. See [elm/http's `Http.stringBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#stringBody).\n\nNote from the `elm/http` docs:\n\n> The first argument is a [MIME type](https://en.wikipedia.org/wiki/Media_type) of the body. Some servers are strict about this!\n\n","type":"String.String -> String.String -> BackendTask.Http.Body"},{"name":"stringPart","comment":" A string part for a multipart body. The first argument is the field name,\nthe second is the string value.\n\n    BackendTask.Http.stringPart \"title\" \"My Photo\"\n\n","type":"String.String -> String.String -> BackendTask.Http.Part"},{"name":"withMetadata","comment":" ","type":"(BackendTask.Http.Metadata -> value -> combined) -> BackendTask.Http.Expect value -> BackendTask.Http.Expect combined"}],"binops":[]},{"name":"BackendTask.Random","comment":"\n\n@docs generate\n\n@docs int32\n\n","unions":[],"aliases":[],"values":[{"name":"generate","comment":" Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.\n\n    type alias Data =\n        { randomData : ( Int, Float )\n        }\n\n    data : BackendTask FatalError Data\n    data =\n        BackendTask.map Data\n            (BackendTask.Random.generate generator)\n\n    generator : Random.Generator ( Int, Float )\n    generator =\n        Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)\n\nThe random initial seed is generated using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>\nto generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.\nThen that `Seed` used to run the `Generator`.\n\nNote that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`\nrandom seed generation is more cryptographically independent because you can't determine the\nrandom seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly\ngenerated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`\ngenerates an initial seed using `Time.now`, and then continues with that same seed using using [`Random.step`](https://package.elm-lang.org/packages/elm/random/latest/Random#step)\nto get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).\nHowever, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it\nsteps through new seeds while running a single `Random.Generator`.\n\n","type":"Random.Generator value -> BackendTask.BackendTask error value"},{"name":"int32","comment":" Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound\nrandom 32-bit integer.\n\nThe value comes from running this code in Node using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>:\n\n```js\nimport * as crypto from \"node:crypto\";\n\ncrypto.getRandomValues(new Uint32Array(1))[0]\n```\n\n","type":"BackendTask.BackendTask error Basics.Int"}],"binops":[]},{"name":"BackendTask.Stream","comment":" A `Stream` represents a flow of data through a pipeline.\n\nIt is typically\n\n  - An input source, or Readable Stream (`Stream { read : (), write : Never }`)\n  - An output destination, or Writable Stream (`Stream { read : Never, write : () }`)\n  - And (optionally) a series of transformations in between, or Duplex Streams (`Stream { read : (), write : () }`)\n\nFor example, you could have a stream that\n\n  - Reads from a file [`fileRead`](#fileRead)\n  - Unzips the contents [`unzip`](#unzip)\n  - Runs a shell command on the contents [`command`](#command)\n  - And writes the result to a network connection [`httpWithInput`](#httpWithInput)\n\nFor example,\n\n    import BackendTask.Stream as Stream exposing (Stream)\n\n    example =\n        Stream.fileRead (Path.fromString \"data.txt\")\n            |> Stream.unzip\n            |> Stream.command \"wc\" [ \"-l\" ]\n            |> Stream.httpWithInput\n                { url = \"http://example.com\"\n                , method = \"POST\"\n                , headers = []\n                , retries = Nothing\n                , timeoutInMs = Nothing\n                }\n            |> Stream.run\n\nEnd example\n\n@docs Stream\n\n@docs pipe\n\n@docs fileRead, fileWrite, fromString, http, httpWithInput, stdin, stdout, stderr\n\n\n## Running Streams\n\n@docs read, readJson, readMetadata, run\n\n@docs Error\n\n\n## Shell Commands\n\nNote that the commands do not execute through a shell but rather directly executes a child process. That means that\nspecial shell syntax will have no effect, but instead will be interpreted as literal characters in arguments to the command.\n\nSo instead of `grep error < log.txt`, you would use\n\n    module GrepErrors exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"log.txt\")\n                |> Stream.pipe (Stream.command \"grep\" [ \"error\" ])\n                |> Stream.stdout\n                |> Stream.run\n            )\n\n@docs command\n\n\n## Command Options\n\n@docs commandWithOptions\n\n@docs StderrOutput\n\n@docs CommandOptions, defaultCommandOptions, allowNon0Status, withOutput, withTimeout\n\n\n## Command Output Strategies\n\nThere are 3 things that effect the output behavior of a command:\n\n  - The verbosity of the `BackendTask` context ([`BackendTask.quiet`](BackendTask#quiet))\n  - Whether the `Stream` output is ignored ([`Stream.run`](#run)), or read ([`Stream.read`](#read))\n  - [`withOutput`](#withOutput) (allows you to use stdout, stderr, or both)\n\nWith `BackendTask.quiet`, the output of the command will not print as it runs, but you still read it in Elm if you read the `Stream` (instead of using [`Stream.run`](#run)).\n\nThere are 3 ways to handle the output of a command:\n\n1.  Read the output but don't print\n2.  Print the output but don't read\n3.  Ignore the output\n\nTo read the output (1), use [`Stream.read`](#read) or [`Stream.readJson`](#readJson). This will give you the output as a String or JSON object.\nRegardless of whether you use `BackendTask.quiet`, the output will be read and returned to Elm.\n\nTo let the output from the command natively print to the console (2), use [`Stream.run`](#run) without setting `BackendTask.quiet`. Based on\nthe command's `withOutput` configuration, either stderr, stdout, or both will print to the console. The native output will\nsometimes be treated more like running the command directly in the terminal, for example `elm make` will print progress\nmessages which will be cleared and updated in place.\n\nTo ignore the output (3), use [`Stream.run`](#run) with `BackendTask.quiet`. This will run the command without printing anything to the console.\nYou can also use [`Stream.read`](#read) and ignore the captured output, but this is less efficient than using `BackendTask.quiet` with `Stream.run`.\n\n\n## Compression Helpers\n\n    module CompressionDemo exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"elm.json\")\n                |> Stream.pipe Stream.gzip\n                |> Stream.pipe (Stream.fileWrite (Path.fromString \"elm.json.gz\"))\n                |> Stream.run\n                |> BackendTask.andThen\n                    (\\_ ->\n                        Stream.fileRead (Path.fromString \"elm.json.gz\")\n                            |> Stream.pipe Stream.unzip\n                            |> Stream.pipe Stream.stdout\n                            |> Stream.run\n                    )\n            )\n\n@docs gzip, unzip\n\n\n## Custom Streams\n\n[`BackendTask.Custom`](BackendTask-Custom) lets you define custom `BackendTask`s from async NodeJS functions in your `custom-backend-task` file.\nSimilarly, you can define custom streams with async functions in your `custom-backend-task` file, returning native NodeJS Streams, and optionally functions to extract metadata.\n\n```js\nimport { Writable, Transform, Readable } from \"node:stream\";\n\nexport async function upperCaseStream(input, { cwd, env, quiet }) {\n  return {\n    metadata: () => \"Hi! I'm metadata from upperCaseStream!\",\n    stream: new Transform({\n      transform(chunk, encoding, callback) {\n        callback(null, chunk.toString().toUpperCase());\n      },\n    }),\n  };\n}\n\nexport async function customReadStream(input) {\n  return new Readable({\n    read(size) {\n      this.push(\"Hello from customReadStream!\");\n      this.push(null);\n    },\n  });\n}\n\nexport async function customWriteStream(input, { cwd, env, quiet }) {\n  return {\n    stream: new Writable({\n      write(chunk, encoding, callback) {\n        console.error(\"...received chunk...\");\n        console.log(chunk.toString());\n        callback();\n      },\n    }),\n    metadata: () => {\n      return \"Hi! I'm metadata from customWriteStream!\";\n    },\n  };\n}\n```\n\n    module CustomStreamDemo exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.customRead \"customReadStream\" Encode.null\n                |> Stream.pipe (Stream.customDuplex \"upperCaseStream\" Encode.null)\n                |> Stream.pipe (Stream.customWrite \"customWriteStream\" Encode.null)\n                |> Stream.run\n            )\n\n    To extract the metadata from the custom stream, you can use the `...WithMeta` functions:\n\n    module CustomStreamDemoWithMeta exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.customReadWithMeta \"customReadStream\" Encode.null Decode.succeed\n                |> Stream.pipe (Stream.customTransformWithMeta \"upperCaseStream\" Encode.null Decode.succeed)\n                |> Stream.readMetadata\n                |> BackendTask.allowFatal\n                |> BackendTask.andThen\n                    (\\metadata ->\n                        Script.log (\"Metadata: \" ++ metadata)\n                    )\n            )\n        --> Script.log \"Metadata: Hi! I'm metadata from upperCaseStream!\"\n\n@docs customRead, customWrite, customDuplex\n\n\n### With Metadata Decoders\n\n@docs customReadWithMeta, customTransformWithMeta, customWriteWithMeta\n\n","unions":[{"name":"CommandOptions","comment":" Configuration for [`commandWithOptions`](#commandWithOptions).\n","args":[],"cases":[]},{"name":"Error","comment":" Running or reading a `Stream` can give one of two kinds of error:\n\n  - `StreamError String` - when something in the middle of the stream fails\n  - `CustomError error body` - when the `Stream` fails with a custom error\n\nA `CustomError` can only come from the final part of the stream.\n\nYou can define your own custom errors by decoding metadata to an `Err` in the `...WithMeta` helpers.\n\n","args":["error","body"],"cases":[["StreamError",["String.String"]],["CustomError",["error","Maybe.Maybe body"]]]},{"name":"StderrOutput","comment":" The output configuration for [`withOutput`](#withOutput). The default is `PrintStderr`.\n\n  - `PrintStderr` - Print (but do not pass along) the `stderr` output of the command. Only `stdout` will be passed along as the body of the stream.\n  - `IgnoreStderr` - Ignore the `stderr` output of the command, only include `stdout`\n  - `MergeStderrAndStdout` - Both `stderr` and `stdout` will be passed along as the body of the stream.\n  - `StderrInsteadOfStdout` - Only `stderr` will be passed along as the body of the stream. `stdout` will be ignored.\n\n","args":[],"cases":[["PrintStderr",[]],["IgnoreStderr",[]],["MergeStderrAndStdout",[]],["StderrInsteadOfStdout",[]]]},{"name":"Stream","comment":" Once you've defined a `Stream`, it can be turned into a `BackendTask` that will run it (and optionally read its output and metadata).\n","args":["error","metadata","kind"],"cases":[]}],"aliases":[],"values":[{"name":"allowNon0Status","comment":" By default, the `Stream` will halt with an error if a command returns a non-zero status code.\n\nWith `allowNon0Status`, the stream will continue without an error if the command returns a non-zero status code.\n\n","type":"BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"},{"name":"command","comment":" Run a command (or `child_process`). The command's output becomes the body of the `Stream`.\n","type":"String.String -> List.List String.String -> BackendTask.Stream.Stream Basics.Int () { read : read, write : write }"},{"name":"commandWithOptions","comment":" Pass in custom [`CommandOptions`](#CommandOptions) to configure the behavior of the command.\n\nFor example, `grep` will return a non-zero status code if it doesn't find any matches. To ignore the non-zero status code and proceed with\nempty output, you can use `allowNon0Status`.\n\n    module GrepErrors exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"log.txt\")\n                |> Stream.pipe\n                    (Stream.commandWithOptions\n                        (Stream.defaultCommandOptions |> Stream.allowNon0Status)\n                        \"grep\"\n                        [ \"error\" ]\n                    )\n                |> Stream.pipe Stream.stdout\n                |> Stream.run\n            )\n\n","type":"BackendTask.Stream.CommandOptions -> String.String -> List.List String.String -> BackendTask.Stream.Stream Basics.Int () { read : read, write : write }"},{"name":"customDuplex","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"customRead","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `ReadableStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"customReadWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : (), write : Basics.Never }"},{"name":"customTransformWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` and metadata function it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : (), write : () }"},{"name":"customWrite","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"customWriteWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` and metadata function it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : Basics.Never, write : () }"},{"name":"defaultCommandOptions","comment":" The default options that are used for [`command`](#command). Used to build up `CommandOptions`\nto pass in to [`commandWithOptions`](#commandWithOptions).\n","type":"BackendTask.Stream.CommandOptions"},{"name":"fileRead","comment":" Open a file's contents as a Stream.\n\n    module ReadFile exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"elm.json\")\n                |> Stream.readJson (Decode.field \"source-directories\" (Decode.list Decode.string))\n                |> BackendTask.allowFatal\n                |> BackendTask.andThen\n                    (\\{ body } ->\n                        Script.log\n                            (\"The source directories are: \"\n                                ++ String.join \", \" body\n                            )\n                    )\n            )\n\nIf you want to read a file but don't need to use any of the other Stream functions, you can use [`BackendTask.File.read`](BackendTask-File#rawFile) instead.\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"fileWrite","comment":" Write a Stream to a file.\n\n    module WriteFile exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"logs.txt\")\n                |> Stream.pipe (Stream.command \"grep\" [ \"error\" ])\n                |> Stream.pipe (Stream.fileWrite (Path.fromString \"errors.txt\"))\n            )\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"fromString","comment":" A handy way to turn either a hardcoded String, or any other value from Elm into a Stream.\n\n    module HelloWorld exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fromString \"Hello, World!\"\n                |> Stream.stdout\n                |> Stream.run\n                |> BackendTask.allowFatal\n            )\n\nA more programmatic use of `fromString` to use the result of a previous `BackendTask` to a `Stream`:\n\n    module HelloWorld exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Glob.fromString \"src/**/*.elm\"\n                |> BackendTask.andThen\n                    (\\elmFiles ->\n                        elmFiles\n                            |> String.join \", \"\n                            |> Stream.fromString\n                            |> Stream.pipe Stream.stdout\n                            |> Stream.run\n                    )\n            )\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"gzip","comment":" Transforms the input with gzip compression.\n\nUnder the hood this builds a Stream using Node's [`zlib.createGzip`](https://nodejs.org/api/zlib.html#zlibcreategzipoptions).\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"http","comment":" Uses a regular HTTP request body (not a `Stream`). Streams the HTTP response body.\n\nIf you want to pass a stream as the request body, use [`httpWithInput`](#httpWithInput) instead.\n\nIf you don't need to stream the response body, you can use the functions from [`BackendTask.Http`](BackendTask-Http) instead.\n\n","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Stream.Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : Basics.Never }"},{"name":"httpWithInput","comment":" Streams the data from the input stream as the body of the HTTP request. The HTTP response body becomes the output stream.\n","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Stream.Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : () }"},{"name":"pipe","comment":" You can build up a pipeline of streams by using the `pipe` function.\n\nThe stream you are piping to must be writable (`{ write : () }`),\nand the stream you are piping from must be readable (`{ read : () }`).\n\n    module HelloWorld exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fromString \"Hello, World!\"\n                |> Stream.stdout\n                |> Stream.run\n            )\n\n","type":"BackendTask.Stream.Stream errorTo metaTo { read : toReadable, write : () } -> BackendTask.Stream.Stream errorFrom metaFrom { read : (), write : fromWriteable } -> BackendTask.Stream.Stream errorTo metaTo { read : toReadable, write : fromWriteable }"},{"name":"read","comment":" Read the body of the `Stream` as text.\n","type":"BackendTask.Stream.Stream error metadata { read : (), write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error String.String } { metadata : metadata, body : String.String }"},{"name":"readJson","comment":" Read the body of the `Stream` as JSON.\n\n    module ReadJson exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Json.Decode as Decode\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"data.json\")\n                |> Stream.readJson (Decode.field \"name\" Decode.string)\n                |> BackendTask.allowFatal\n                |> BackendTask.andThen\n                    (\\{ body } ->\n                        Script.log (\"The name is: \" ++ body)\n                    )\n            )\n\n","type":"Json.Decode.Decoder value -> BackendTask.Stream.Stream error metadata { read : (), write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error value } { metadata : metadata, body : value }"},{"name":"readMetadata","comment":" Ignore the body of the `Stream`, while capturing the metadata from the final part of the Stream.\n","type":"BackendTask.Stream.Stream error metadata { read : read, write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error String.String } metadata"},{"name":"run","comment":" Gives a `BackendTask` to execute the `Stream`, ignoring its body and metadata.\n\nThis is useful if you only want the side-effect from the `Stream` and don't need to programmatically use its\noutput. For example, if the end result you want is:\n\n  - Printing to the console\n  - Writing to a file\n  - Making an HTTP request\n\nIf you need to read the output of the `Stream`, use [`read`](#read), [`readJson`](#readJson), or [`readMetadata`](#readMetadata) instead.\n\n","type":"BackendTask.Stream.Stream error metadata kind -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"stderr","comment":" Similar to [`stdout`](#stdout), but writes to `stderr` instead.\n","type":"BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"stdin","comment":" The `stdin` from the process. When you execute an `elm-pages` script, this will be the value that is piped in to it. For example, given this script module:\n\n    module CountLines exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.stdin\n                |> Stream.read\n                |> BackendTask.allowFatal\n                |> BackendTask.andThen\n                    (\\{ body } ->\n                        body\n                            |> String.lines\n                            |> List.length\n                            |> String.fromInt\n                            |> Script.log\n                    )\n            )\n\nIf you run the script without any stdin, it will wait until stdin is closed.\n\n```shell\nelm-pages run script/src/CountLines.elm\n# pressing ctrl-d (or your platform-specific way of closing stdin) will print the number of lines in the input\n```\n\nOr you can pipe to it and it will read that input:\n\n```shell\nls | elm-pages run script/src/CountLines.elm\n# prints the number of files in the current directory\n```\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"stdout","comment":" Streaming through to stdout can be a convenient way to print a pipeline directly without going through to Elm.\n\n    module UnzipFile exposing (run)\n\n    import BackendTask\n    import BackendTask.Stream as Stream\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Stream.fileRead (Path.fromString \"data.gzip.txt\")\n                |> Stream.pipe Stream.unzip\n                |> Stream.pipe Stream.stdout\n                |> Stream.run\n                |> BackendTask.allowFatal\n            )\n\n","type":"BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"unzip","comment":" Transforms the input by auto-detecting the header and decompressing either a Gzip- or Deflate-compressed stream.\n\nUnder the hood, this builds a Stream using Node's [`zlib.createUnzip`](https://nodejs.org/api/zlib.html#zlibcreateunzip).\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"withOutput","comment":" Configure the [`StderrOutput`](#StderrOutput) behavior.\n","type":"BackendTask.Stream.StderrOutput -> BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"},{"name":"withTimeout","comment":" By default, commands do not have a timeout. This will set the timeout, in milliseconds, for the given command. If that duration is exceeded,\nthe `Stream` will fail with an error.\n","type":"Basics.Int -> BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"}],"binops":[]},{"name":"BackendTask.Time","comment":"\n\n@docs now\n\n@docs zone, zoneFor, zoneByName, zoneByNameFor\n\n@docs DateRange, withinYears, withinRange, between\n\n","unions":[{"name":"DateRange","comment":" A date range that specifies which period of time to include timezone transition data for.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"between","comment":" An exact date range using [`Date`](https://package.elm-lang.org/packages/justinmimbs/date/latest/Date) values.\n\n    import Date\n    import Time\n\n    BackendTask.Time.zoneFor\n        (BackendTask.Time.between\n            { since = Date.fromCalendarDate 2020 Time.Jan 1\n            , until = Date.fromCalendarDate 2030 Time.Dec 31\n            }\n        )\n\n","type":"{ since : Date.Date, until : Date.Date } -> BackendTask.Time.DateRange"},{"name":"now","comment":" Gives a `Time.Posix` of when the `BackendTask` executes.\n\n    type alias Data =\n        { time : Time.Posix\n        }\n\n    data : BackendTask FatalError Data\n    data =\n        BackendTask.map Data\n            BackendTask.Time.now\n\nIt's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics\nyou are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in\na server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the\nsite was built.\n\n`BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be\naware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and\nits best to avoid depending on that variation when possible.\n\n","type":"BackendTask.BackendTask error Time.Posix"},{"name":"withinRange","comment":" An asymmetric date range relative to the current time.\n\n    BackendTask.Time.zoneFor (BackendTask.Time.withinRange { yearsAgo = 10, yearsAhead = 2 })\n\n","type":"{ yearsAgo : Basics.Int, yearsAhead : Basics.Int } -> BackendTask.Time.DateRange"},{"name":"withinYears","comment":" A symmetric date range: N years before and after the current time.\n\n    BackendTask.Time.zoneFor (BackendTask.Time.withinYears 5)\n\n","type":"Basics.Int -> BackendTask.Time.DateRange"},{"name":"zone","comment":" Get the server's local [`Time.Zone`](https://package.elm-lang.org/packages/elm/time/latest/Time#Zone)\nwith DST transitions covering 50 years in each direction from the current time.\n\n    import BackendTask.Time\n\n    myZone : BackendTask error Time.Zone\n    myZone =\n        BackendTask.Time.zone\n\nThis is the simplest way to get a timezone with correct DST handling. The 50-year default\ncovers the vast majority of use cases.\n\n**Performance:** Computing timezone transitions takes roughly 35-80ms depending on how many\nDST transitions the timezone has (timezones without DST like UTC or Asia/Kolkata are fastest).\nIf you need a narrower or wider range, use [`zoneFor`](#zoneFor) with a [`DateRange`](#DateRange).\nFor reference, `withinYears 5` takes ~10ms, `withinYears 50` (this default) ~40-80ms, and\n`withinYears 100` ~70-130ms.\n\n**Note:** This returns the timezone of the _server_ (or build machine), not the client's browser timezone.\nThis makes it particularly useful for scripting and build-time tasks where you want to format dates in\nthe server's local time. It is unlikely to be useful for resolving timezones for server-rendered routes,\nsince the server's timezone will generally not match the end user's timezone.\n\n","type":"BackendTask.BackendTask error Time.Zone"},{"name":"zoneByName","comment":" Get a [`Time.Zone`](https://package.elm-lang.org/packages/elm/time/latest/Time#Zone) for a specific\nIANA timezone (e.g. `\"America/New_York\"`, `\"Europe/London\"`, `\"Asia/Tokyo\"`) with DST transitions\ncovering 50 years in each direction.\n\n    import BackendTask.Time\n\n    newYorkZone : BackendTask error Time.Zone\n    newYorkZone =\n        BackendTask.Time.zoneByName \"America/New_York\"\n\nThis is useful for formatting dates in a known timezone regardless of what timezone the server is running in.\nFor example, a blog built on a CI server (typically UTC) can format post dates in the author's local timezone.\n\nIf you need a specific date range, use [`zoneByNameFor`](#zoneByNameFor).\n\nAn invalid timezone name (e.g. `\"Foo/Bar\"`) will result in a `FatalError`.\n\n","type":"String.String -> BackendTask.BackendTask error Time.Zone"},{"name":"zoneByNameFor","comment":" Like [`zoneByName`](#zoneByName), but with a specific [`DateRange`](#DateRange).\n\n    import BackendTask.Time exposing (between)\n    import Date\n    import Time\n\n    newYorkZone : BackendTask error Time.Zone\n    newYorkZone =\n        BackendTask.Time.zoneByNameFor \"America/New_York\"\n            (between\n                { since = Date.fromCalendarDate 2015 Time.Jan 1\n                , until = Date.fromCalendarDate 2030 Time.Dec 31\n                }\n            )\n\n","type":"String.String -> BackendTask.Time.DateRange -> BackendTask.BackendTask error Time.Zone"},{"name":"zoneFor","comment":" Get the server's local [`Time.Zone`](https://package.elm-lang.org/packages/elm/time/latest/Time#Zone)\nwith DST transitions for a specific date range.\n\n    import BackendTask.Time exposing (between)\n    import Date\n    import Time\n\n    myZone : BackendTask error Time.Zone\n    myZone =\n        BackendTask.Time.zoneFor\n            (between\n                { since = Date.fromCalendarDate 2020 Time.Jan 1\n                , until = Date.fromCalendarDate 2030 Time.Dec 31\n                }\n            )\n\nUse this instead of [`zone`](#zone) when you want to limit the range of DST transition data,\nfor example to a narrower window around the dates you need.\n\n**Note:** This returns the timezone of the _server_ (or build machine), not the client's browser timezone.\n\n","type":"BackendTask.Time.DateRange -> BackendTask.BackendTask error Time.Zone"}],"binops":[]},{"name":"FatalError","comment":" The Elm language doesn't have the concept of exceptions or special control flow for errors. It just has\nCustom Types, and by convention types like `Result` and the `Err` variant are used to represent possible failure states\nand combine together different error states.\n\n`elm-pages` doesn't change that, Elm still doesn't have special exception control flow at the language level. It does have\na type, which is just a regular old Elm type, called `FatalError`. Why? Because this plain old Elm type does have one\nspecial characteristic - the `elm-pages` framework knows how to turn it into an error message. This becomes interesting\nbecause an `elm-pages` app has several places that accept a value of type `BackendTask FatalError.FatalError value`.\nThis design lets the `elm-pages` framework do some of the work for you.\n\nFor example, if you wanted to handle possible errors to present them to the user\n\n    type alias Data =\n        String\n\n    data : RouteParams -> BackendTask FatalError Data\n    data routeParams =\n        BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.field \"description\" Decode.string)\n            |> BackendTask.onError\n                (\\{ recoverable } ->\n                    case recoverable of\n                        BackendTask.Http.BadStatus metadata string ->\n                            if metadata.statusCode == 401 || metadata.statusCode == 403 || metadata.statusCode == 404 then\n                                BackendTask.succeed \"Either this repo doesn't exist or you don't have access to it.\"\n\n                            else\n                                -- we're only handling these expected error cases. In the case of an HTTP timeout,\n                                -- we'll let the error propagate as a FatalError\n                                BackendTask.fail error |> BackendTask.allowFatal\n\n                        _ ->\n                            BackendTask.fail error |> BackendTask.allowFatal\n                )\n\nThis can be a lot of work for all possible errors, though. If you don't expect this kind of error (it's an _exceptional_ case),\nyou can let the framework handle it if the error ever does unexpectedly occur.\n\n    data : RouteParams -> BackendTask FatalError Data\n    data routeParams =\n        BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.field \"description\" Decode.string)\n            |> BackendTask.allowFatal\n\nThis is especially useful for pages generated at build-time (`RouteBuilder.preRender`) where you want the build\nto fail if anything unexpected happens. With pre-rendered routes, you know that these error cases won't\nbe seen by users, so it's often a great idea to just let the framework handle these unexpected errors so a developer can\ndebug them and see what went wrong. In the example above, maybe we are only pre-rendering pages for a set of known\nGitHub Repositories, so a Not Found or Unauthorized HTTP error would be unexpected and should stop the build so we can fix the\nissue.\n\nIn the case of server-rendered Routes (`RouteBuilder.serverRender`), `elm-pages` will show your 500 error page\nwhen these errors occur.\n\n@docs FatalError, build, fromString, recoverable\n\n","unions":[],"aliases":[{"name":"FatalError","comment":" ","args":[],"type":"Pages.Internal.FatalError.FatalError"}],"values":[{"name":"build","comment":" Create a FatalError with a title and body.\n","type":"{ title : String.String, body : String.String } -> FatalError.FatalError"},{"name":"fromString","comment":" ","type":"String.String -> FatalError.FatalError"},{"name":"recoverable","comment":" ","type":"{ title : String.String, body : String.String } -> error -> { fatal : FatalError.FatalError, recoverable : error }"}],"binops":[]},{"name":"FilePath","comment":" Cross-platform file path utilities for `BackendTask.File` and `Pages.Script`.\n\nThe representation is opaque so we can evolve path behavior over time without exposing\nimplementation details.\n\n@docs FilePath\n\n@docs fromString, relative, absolute\n\n@docs toString\n\n@docs segments\n\n@docs append, join\n\n@docs dirname, filename, filenameWithoutExtension, extension\n\n@docs relativeTo\n\n@docs resolve\n\n","unions":[{"name":"FilePath","comment":" Opaque file path type.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"absolute","comment":" Build an absolute path from segments.\n\n    FilePath.absolute [ \"usr\", \"bin\" ]\n        |> FilePath.toString\n    --> \"/usr/bin\"\n\n","type":"List.List String.String -> FilePath.FilePath"},{"name":"append","comment":" Append a path to a base path.\n\nIf `nextPath` has a root marker (absolute path, UNC path, or drive prefix), it replaces\nthe base path.\n\n","type":"FilePath.FilePath -> FilePath.FilePath -> FilePath.FilePath"},{"name":"dirname","comment":" Parent directory of a path.\n","type":"FilePath.FilePath -> Maybe.Maybe FilePath.FilePath"},{"name":"extension","comment":" File extension without the dot.\n","type":"FilePath.FilePath -> Maybe.Maybe String.String"},{"name":"filename","comment":" The final path segment, if any.\n","type":"FilePath.FilePath -> Maybe.Maybe String.String"},{"name":"filenameWithoutExtension","comment":" Filename without extension.\n","type":"FilePath.FilePath -> Maybe.Maybe String.String"},{"name":"fromString","comment":" Build a path from a string. Accepts both `/` and `\\\\` separators.\n\nThis normalizes the path to a stable representation:\n\n  - Converts `\\\\` to `/`\n  - Collapses duplicate separators\n  - Removes `.` segments\n  - Resolves `..` segments when possible\n  - Removes trailing separators (except for roots)\n\nIt accepts both relative and absolute paths.\n\n","type":"String.String -> FilePath.FilePath"},{"name":"join","comment":" Join many paths from left to right.\n","type":"List.List FilePath.FilePath -> FilePath.FilePath"},{"name":"relative","comment":" Build a relative path from segments.\n\n    FilePath.relative [ \"src\", \"Main.elm\" ]\n        |> FilePath.toString\n    --> \"src/Main.elm\"\n\n","type":"List.List String.String -> FilePath.FilePath"},{"name":"relativeTo","comment":" Path to `targetPath` relative to `basePath`.\n\nReturns `Nothing` when roots differ.\n\n    fromString \"/foo/bar/qux\"\n        |> relativeTo (fromString \"/foo\")\n        --> Just (fromString \"bar/qux\")\n\n","type":"FilePath.FilePath -> FilePath.FilePath -> Maybe.Maybe FilePath.FilePath"},{"name":"resolve","comment":" Resolve a file path to an absolute path using the OS-aware `path.resolve` from Node.js.\n\nRelative paths are resolved against the current working directory.\n\n    import BackendTask exposing (BackendTask)\n    import FatalError exposing (FatalError)\n    import FilePath\n\n    resolveExample : BackendTask FatalError FilePath\n    resolveExample =\n        FilePath.fromString \"src/Main.elm\"\n            |> FilePath.resolve\n\n","type":"FilePath.FilePath -> BackendTask.BackendTask FatalError.FatalError FilePath.FilePath"},{"name":"segments","comment":" Return path segments without root markers.\n","type":"FilePath.FilePath -> List.List String.String"},{"name":"toString","comment":" Convert a path to a string. Uses `/` separators.\n","type":"FilePath.FilePath -> String.String"}],"binops":[]},{"name":"Head","comment":" This module contains functions for building up\ntags with metadata that will be rendered into the page's `<head>` tag\nwhen your page is pre-rendered (or server-rendered, in the case of your server-rendered Route Modules). See also [`Head.Seo`](Head-Seo),\nwhich has some helper functions for defining OpenGraph and Twitter tags.\n\nOne of the unique benefits of using `elm-pages` is that all of your routes (both pre-rendered and server-rendered) fully\nrender the HTML of your page. That includes the full initial `view` (with the BackendTask resolved, and the `Model` from `init`).\nThe HTML response also includes all of the `Head` tags, which are defined in two places:\n\n1.  `app/Site.elm` - there is a `head` definition in `Site.elm` where you define global head tags that will be included on every rendered page.\n\n2.  In each Route Module - there is a `head` function where you have access to both the resolved `BackendTask` and the `RouteParams` for the page and can return head tags based on that.\n\nHere is a common set of global head tags that we can define in `Site.elm`:\n\n    module Site exposing (canonicalUrl, config)\n\n    import BackendTask exposing (BackendTask)\n    import Head\n    import MimeType\n    import SiteConfig exposing (SiteConfig)\n\n    config : SiteConfig\n    config =\n    { canonicalUrl = \"<https://elm-pages.com\">\n    , head = head\n    }\n\n    head : BackendTask (List Head.Tag)\n    head =\n    [ Head.metaName \"viewport\" (Head.raw \"width=device-width,initial-scale=1\")\n    , Head.metaName \"mobile-web-app-capable\" (Head.raw \"yes\")\n    , Head.metaName \"theme-color\" (Head.raw \"#ffffff\")\n    , Head.metaName \"apple-mobile-web-app-capable\" (Head.raw \"yes\")\n    , Head.metaName \"apple-mobile-web-app-status-bar-style\" (Head.raw \"black-translucent\")\n    , Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)\n    , Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)\n    , Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)\n    , Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)\n    ]\n    |> BackendTask.succeed\n\nAnd here is a `head` function for a Route Module for a blog post. Note that we have access to our `BackendTask` Data and\nare using it to populate article metadata like the article's image, publish date, etc.\n\n    import Article\n    import BackendTask\n    import Date\n    import Head\n    import Head.Seo\n    import Path\n    import Route exposing (Route)\n    import RouteBuilder exposing (App, StatelessRoute)\n\n    type alias RouteParams =\n        { slug : String }\n\n    type alias Data =\n        { metadata : ArticleMetadata\n        , body : List Markdown.Block.Block\n        }\n\n    route : StatelessRoute RouteParams Data ActionData\n    route =\n        RouteBuilder.preRender\n            { data = data\n            , head = head\n            , pages = pages\n            }\n            |> RouteBuilder.buildNoState { view = view }\n\n    head :\n        App Data ActionData RouteParams\n        -> List Head.Tag\n    head static =\n        let\n            metadata =\n                static.data.metadata\n        in\n        Head.Seo.summaryLarge\n            { canonicalUrlOverride = Nothing\n            , siteName = \"elm-pages\"\n            , image =\n                { url = metadata.image\n                , alt = metadata.description\n                , dimensions = Nothing\n                , mimeType = Nothing\n                }\n            , description = metadata.description\n            , locale = Nothing\n            , title = metadata.title\n            }\n            |> Head.Seo.article\n                { tags = []\n                , section = Nothing\n                , publishedTime = Just (DateOrDateTime.Date metadata.published)\n                , modifiedTime = Nothing\n                , expirationTime = Nothing\n                }\n\n\n## Why is pre-rendered HTML important? Does it still matter for SEO?\n\nMany search engines are able to execute JavaScript now. However, not all are, and even with crawlers like Google, there\nis a longer lead time for your pages to be indexed when you have HTML with a blank page that is only visible after the JavaScript executes.\n\nBut most importantly, many tools that unfurl links will not execute JavaScript at all, but rather simply do a simple pass to parse your `<head>` tags.\nIt is not viable or reliable to add `<head>` tags for metadata on the client-side, it must be present in the initial HTML payload. Otherwise you may not\nget unfurling preview content when you share a link to your site on Slack, Twitter, etc.\n\n\n## Building up Head Tags\n\n@docs Tag, metaName, metaProperty, metaRedirect\n@docs rssLink, sitemapLink, rootLanguage, manifestLink\n\n@docs nonLoadingNode\n\n\n## Structured Data\n\n@docs structuredData\n\n\n## `AttributeValue`s\n\n@docs AttributeValue\n@docs currentPageFullUrl, urlAttribute, raw\n\n\n## Icons\n\n@docs appleTouchIcon, icon\n\n\n## Functions for use by generated code\n\n@docs toJson, canonicalLink\n\n","unions":[{"name":"AttributeValue","comment":" Values, such as between the `<>`'s here:\n\n```html\n<meta name=\"<THIS IS A VALUE>\" content=\"<THIS IS A VALUE>\" />\n```\n\n","args":[],"cases":[]},{"name":"Tag","comment":" Values that can be passed to the generated `Pages.application` config\nthrough the `head` function.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"appleTouchIcon","comment":" Note: the type must be png.\nSee <https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html>.\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: <https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/#safari>\n\nImages must be png's, and non-transparent images are recommended. Current recommended dimensions are 180px and 192px.\n\n","type":"Maybe.Maybe Basics.Int -> Pages.Url.Url -> Head.Tag"},{"name":"canonicalLink","comment":" It's recommended that you use the `Seo` module helpers, which will provide this\nfor you, rather than directly using this.\n\nExample:\n\n    Head.canonicalLink \"https://elm-pages.com\"\n\n","type":"Maybe.Maybe String.String -> Head.Tag"},{"name":"currentPageFullUrl","comment":" Create an `AttributeValue` representing the current page's full url.\n","type":"Head.AttributeValue"},{"name":"icon","comment":" ","type":"List.List ( Basics.Int, Basics.Int ) -> MimeType.MimeImage -> Pages.Url.Url -> Head.Tag"},{"name":"manifestLink","comment":" Let's you link to your manifest.json file, see <https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest>.\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n    Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in `<meta name=\"twitter:card\" content=\"summary_large_image\" />`\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaProperty","comment":" Example:\n\n    Head.metaProperty \"fb:app_id\" (Head.raw \"123456789\")\n\nResults in `<meta property=\"fb:app_id\" content=\"123456789\" />`\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaRedirect","comment":" Example:\n\n    metaRedirect (Raw \"0; url=https://google.com\")\n\nResults in `<meta http-equiv=\"refresh\" content=\"0; url=https://google.com\" />`\n\n","type":"Head.AttributeValue -> Head.Tag"},{"name":"nonLoadingNode","comment":" Escape hatch for any head tags that don't have high-level helpers. This lets you build arbitrary head nodes as long as they\nare not loading or preloading directives.\n\nTags that do loading/pre-loading will not work from this function. `elm-pages` uses ViteJS for loading assets like\nscript tags, stylesheets, fonts, etc., and allows you to customize which assets to preload and how through the elm-pages.config.mjs file.\nSee the full discussion of the design in [#339](https://github.com/dillonkearns/elm-pages/discussions/339).\n\nSo for example the following tags would _not_ load if defined through `nonLoadingNode`, and would instead need to be registered through Vite:\n\n  - `<script src=\"...\">`\n  - `<link href=\"/style.css\">`\n  - `<link rel=\"preload\">`\n\nThe following tag would successfully render as it is a non-loading tag:\n\n    Head.nonLoadingNode \"link\"\n        [ ( \"rel\", Head.raw \"alternate\" )\n        , ( \"type\", Head.raw \"application/atom+xml\" )\n        , ( \"title\", Head.raw \"News\" )\n        , ( \"href\", Head.raw \"/news/atom\" )\n        ]\n\nRenders the head tag:\n\n`<link rel=\"alternate\" type=\"application/atom+xml\" title=\"News\" href=\"/news/atom\">`\n\n","type":"String.String -> List.List ( String.String, Head.AttributeValue ) -> Head.Tag"},{"name":"raw","comment":" Create a raw `AttributeValue` (as opposed to some kind of absolute URL).\n","type":"String.String -> Head.AttributeValue"},{"name":"rootLanguage","comment":" Set the language for a page.\n\n<https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>\n\n    import Head\n    import LanguageTag\n    import LanguageTag.Language\n\n    LanguageTag.Language.de -- sets the page's language to German\n        |> LanguageTag.build LanguageTag.emptySubtags\n        |> Head.rootLanguage\n\nThis results pre-rendered HTML with a global lang tag set.\n\n```html\n<html lang=\"no\">\n...\n</html>\n```\n\n","type":"LanguageTag.LanguageTag -> Head.Tag"},{"name":"rssLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n    rssLink \"/feed.xml\"\n\n```html\n<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"sitemapLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n    sitemapLink \"/feed.xml\"\n\n```html\n<link rel=\"sitemap\" type=\"application/xml\" href=\"/sitemap.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"structuredData","comment":" You can learn more about structured data in [Google's intro to structured data](https://developers.google.com/search/docs/guides/intro-structured-data).\n\nWhen you add a `structuredData` item to one of your pages in `elm-pages`, it will add `json-ld` data to your document that looks like this:\n\n```html\n<script type=\"application/ld+json\">\n{\n   \"@context\":\"http://schema.org/\",\n   \"@type\":\"Article\",\n   \"headline\":\"Extensible Markdown Parsing in Pure Elm\",\n   \"description\":\"Introducing a new parser that extends your palette with no additional syntax\",\n   \"image\":\"https://elm-pages.com/images/article-covers/extensible-markdown-parsing.jpg\",\n   \"author\":{\n      \"@type\":\"Person\",\n      \"name\":\"Dillon Kearns\"\n   },\n   \"publisher\":{\n      \"@type\":\"Person\",\n      \"name\":\"Dillon Kearns\"\n   },\n   \"url\":\"https://elm-pages.com/blog/extensible-markdown-parsing-in-elm\",\n   \"datePublished\":\"2019-10-08\",\n   \"mainEntityOfPage\":{\n      \"@type\":\"SoftwareSourceCode\",\n      \"codeRepository\":\"https://github.com/dillonkearns/elm-pages\",\n      \"description\":\"A statically typed site generator for Elm.\",\n      \"author\":\"Dillon Kearns\",\n      \"programmingLanguage\":{\n         \"@type\":\"ComputerLanguage\",\n         \"url\":\"http://elm-lang.org/\",\n         \"name\":\"Elm\",\n         \"image\":\"http://elm-lang.org/\",\n         \"identifier\":\"http://elm-lang.org/\"\n      }\n   }\n}\n</script>\n```\n\nTo get that data, you would write this in your `elm-pages` head tags:\n\n    import Json.Encode as Encode\n\n    {-| <https://schema.org/Article>\n    -}\n    encodeArticle :\n        { title : String\n        , description : String\n        , author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields\n        , publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields\n        , url : String\n        , imageUrl : String\n        , datePublished : String\n        , mainEntityOfPage : Encode.Value\n        }\n        -> Head.Tag\n    encodeArticle info =\n        Encode.object\n            [ ( \"@context\", Encode.string \"http://schema.org/\" )\n            , ( \"@type\", Encode.string \"Article\" )\n            , ( \"headline\", Encode.string info.title )\n            , ( \"description\", Encode.string info.description )\n            , ( \"image\", Encode.string info.imageUrl )\n            , ( \"author\", encode info.author )\n            , ( \"publisher\", encode info.publisher )\n            , ( \"url\", Encode.string info.url )\n            , ( \"datePublished\", Encode.string info.datePublished )\n            , ( \"mainEntityOfPage\", info.mainEntityOfPage )\n            ]\n            |> Head.structuredData\n\nTake a look at this [Google Search Gallery](https://developers.google.com/search/docs/guides/search-gallery)\nto see some examples of how structured data can be used by search engines to give rich search results. It can help boost\nyour rankings, get better engagement for your content, and also make your content more accessible. For example,\nvoice assistant devices can make use of structured data. If you're hosting a conference and want to make the event\ndate and location easy for attendees to find, this can make that information more accessible.\n\nFor the current version of API, you'll need to make sure that the format is correct and contains the required and recommended\nstructure.\n\nCheck out <https://schema.org> for a comprehensive listing of possible data types and fields. And take a look at\nGoogle's [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool)\ntoo make sure that your structured data is valid and includes the recommended values.\n\nIn the future, `elm-pages` will likely support a typed API, but schema.org is a massive spec, and changes frequently.\nAnd there are multiple sources of information on the possible and recommended structure. So it will take some time\nfor the right API design to evolve. In the meantime, this allows you to make use of this for SEO purposes.\n\n","type":"Json.Encode.Value -> Head.Tag"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> String.String -> Head.Tag -> Json.Encode.Value"},{"name":"urlAttribute","comment":" Create an `AttributeValue` from an `ImagePath`.\n","type":"Pages.Url.Url -> Head.AttributeValue"}],"binops":[]},{"name":"Head.Seo","comment":" <https://developers.facebook.com/docs/sharing/opengraph>\n\nThis module encapsulates some of the best practices for SEO for your site.\n\n`elm-pages` pre-renders the HTML for your pages (either at build-time or server-render time) so that\nweb crawlers can efficiently and accurately process it. The functions in this module are for use\nwith the `head` function in your `Route` modules to help you build up a set of `<meta>` tags that\nincludes common meta tags used for rich link previews, namely [OpenGraph tags](https://ogp.me/) and [Twitter card tags](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards).\n\n    import Date\n    import Head\n    import Head.Seo as Seo\n\n\n    -- justinmimbs/date package\n    type alias ArticleMetadata =\n        { title : String\n        , description : String\n        , published : Date\n        , author : Data.Author.Author\n        }\n\n    head : ArticleMetadata -> List Head.Tag\n    head articleMetadata =\n        Seo.summaryLarge\n            { canonicalUrlOverride = Nothing\n            , siteName = \"elm-pages\"\n            , image =\n                { url = Pages.images.icon\n                , alt = articleMetadata.description\n                , dimensions = Nothing\n                , mimeType = Nothing\n                }\n            , description = articleMetadata.description\n            , locale = Nothing\n            , title = articleMetadata.title\n            }\n            |> Seo.article\n                { tags = []\n                , section = Nothing\n                , publishedTime = Just (Date.toIsoString articleMetadata.published)\n                , modifiedTime = Nothing\n                , expirationTime = Nothing\n                }\n\n@docs Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website\n\n","unions":[],"aliases":[{"name":"Common","comment":" These fields apply to any type in the og object types\nSee <https://ogp.me/#metadata> and <https://ogp.me/#optional>\n\nSkipping this for now, if there's a use case I can add it in:\n\n  - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, \"\", auto). If auto is chosen, the consumer of your data should chose between \"a\" or \"an\". Default is \"\" (blank).\n\n","args":[],"type":"{ title : String.String, image : Head.Seo.Image, canonicalUrlOverride : Maybe.Maybe String.String, description : String.String, siteName : String.String, audio : Maybe.Maybe Head.Seo.Audio, video : Maybe.Maybe Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale, alternateLocales : List.List Head.Seo.Locale, twitterCard : Head.Twitter.TwitterCard }"},{"name":"Image","comment":" See <https://ogp.me/#structured>\n","args":[],"type":"{ url : Pages.Url.Url, alt : String.String, dimensions : Maybe.Maybe { width : Basics.Int, height : Basics.Int }, mimeType : Maybe.Maybe MimeType.MimeType }"}],"values":[{"name":"article","comment":" See <https://ogp.me/#type_article>\n","type":"{ tags : List.List String.String, section : Maybe.Maybe String.String, publishedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, modifiedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, expirationTime : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"audioPlayer","comment":" Will be displayed as a Player card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, audio : Head.Seo.Audio, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"book","comment":" See <https://ogp.me/#type_book>\n","type":"Head.Seo.Common -> { tags : List.List String.String, isbn : Maybe.Maybe String.String, releaseDate : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> List.List Head.Tag"},{"name":"profile","comment":" See <https://ogp.me/#type_profile>\n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See <https://ogp.me/#type_music.song>\n","type":"Head.Seo.Common -> { duration : Maybe.Maybe Basics.Int, album : Maybe.Maybe Basics.Int, disc : Maybe.Maybe Basics.Int, track : Maybe.Maybe Basics.Int } -> List.List Head.Tag"},{"name":"summary","comment":" Will be displayed as a large card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"summaryLarge","comment":" Will be displayed as a large card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"videoPlayer","comment":" Will be displayed as a Player card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, video : Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"website","comment":" <https://ogp.me/#type_website>\n","type":"Head.Seo.Common -> List.List Head.Tag"}],"binops":[]},{"name":"Pages.ConcurrentSubmission","comment":" When you render a `Form` with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) `Option`, the state of in-flight and completed submissions will be available\nfrom your `Route` module through `app.concurrentSubmissions` as a `Dict String (ConcurrentSubmission (Maybe Action))`.\n\nYou can use this state to declaratively derive Pending UI or Optimistic UI from your pending submissions (without managing the state in your `Model`, since `elm-pages`\nmanages form submission state for you).\n\nYou can [see the full-stack TodoMVC example](https://github.com/dillonkearns/elm-pages/blob/master/examples/todos/app/Route/Visibility__.elm) for a complete example of deriving Pending UI state from `app.concurrentSubmissions`.\n\nFor example, this how the TodoMVC example derives the list of new items that are being created (but are still pending).\n\n    view :\n        App Data ActionData RouteParams\n        -> Shared.Model\n        -> Model\n        -> View (PagesMsg Msg)\n    view app shared model =\n        let\n            pendingActions : List Action\n            pendingActions =\n                app.concurrentSubmissions\n                    |> Dict.values\n                    |> List.filterMap\n                        (\\{ status, payload } ->\n                            case status of\n                                Pages.ConcurrentSubmission.Complete _ ->\n                                    Nothing\n\n                                _ ->\n                                    allForms\n                                        |> Form.Handler.run payload.fields\n                                        |> Form.toResult\n                                        |> Result.toMaybe\n                        )\n\n            newPendingItems : List Entry\n            newPendingItems =\n                pendingActions\n                    |> List.filterMap\n                        (\\submission ->\n                            case submission of\n                                Add description ->\n                                    Just\n                                        { description = description\n                                        , completed = False\n                                        , id = \"\"\n                                        , isPending = True\n                                        }\n\n                                _ ->\n                                -- `newPendingItems` only cares about pending Add actions. Other values will use\n                                -- pending submissions for other types of Actions.\n                                    Nothing\n                        )\n        in\n        itemsView app newPendingItems\n\n        allForms : Form.Handler.Handler String Action\n        allForms =\n                |> Form.Handler.init Add addItemForm\n                -- |> Form.Handler.with ...\n\n\n        type Action\n            = UpdateEntry ( String, String )\n            | Add String\n            | Delete String\n            | DeleteComplete\n            | Check ( Bool, String )\n            | CheckAll Bool\n\n@docs ConcurrentSubmission, Status\n\n@docs map\n\n","unions":[{"name":"Status","comment":" The status of a `ConcurrentSubmission`.\n\n  - `Submitting` - The submission is in-flight.\n  - `Reloading` - The submission has completed, and the page is now reloading the `Route`'s `data` to reflect the new state. The `actionData` holds any data returned from the `Route`'s `action`.\n  - `Complete` - The submission has completed, and the `Route`'s `data` has since reloaded so the state reflects the refreshed state after completing this specific form submission. The `actionData` holds any data returned from the `Route`'s `action`.\n\n","args":["actionData"],"cases":[["Submitting",[]],["Reloading",["actionData"]],["Complete",["actionData"]]]}],"aliases":[{"name":"ConcurrentSubmission","comment":" ","args":["actionData"],"type":"{ status : Pages.ConcurrentSubmission.Status actionData, payload : Pages.FormData.FormData, initiatedAt : Time.Posix }"}],"values":[{"name":"map","comment":" `map` a `ConcurrentSubmission`. Not needed for most high-level cases since this state is managed by the `elm-pages` framework for you.\n","type":"(a -> b) -> Pages.ConcurrentSubmission.ConcurrentSubmission a -> Pages.ConcurrentSubmission.ConcurrentSubmission b"}],"binops":[]},{"name":"Pages.Fetcher","comment":"\n\n@docs Fetcher, FetcherInfo, submit, map\n\n","unions":[{"name":"Fetcher","comment":" ","args":["decoded"],"cases":[["Fetcher",["Pages.Fetcher.FetcherInfo decoded"]]]}],"aliases":[{"name":"FetcherInfo","comment":" ","args":["decoded"],"type":"{ decoder : Result.Result Http.Error Bytes.Bytes -> decoded, fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ), url : Maybe.Maybe String.String }"}],"values":[{"name":"map","comment":" ","type":"(a -> b) -> Pages.Fetcher.Fetcher a -> Pages.Fetcher.Fetcher b"},{"name":"submit","comment":" ","type":"Bytes.Decode.Decoder decoded -> { fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ) } -> Pages.Fetcher.Fetcher (Result.Result Http.Error decoded)"}],"binops":[]},{"name":"Pages.Flags","comment":"\n\n@docs Flags\n\n","unions":[{"name":"Flags","comment":" elm-pages apps run in two different contexts\n\n1.  In the browser (like a regular Elm app)\n2.  In pre-render mode. For example when you run `elm-pages build`, there is no browser involved, it just runs Elm directly.\n\nYou can pass in Flags and use them in your `Shared.init` function. You can store data in your `Shared.Model` from these flags and then access it across any page.\n\nYou will need to handle the `PreRender` case with no flags value because there is no browser to get flags from. For example, say you wanted to get the\ncurrent user's Browser window size and pass it in as a flag. When that page is pre-rendered, you need to decide on a value to use for the window size\nsince there is no window (the user hasn't requested the page yet, and the page isn't even loaded in a Browser window yet).\n\n","args":[],"cases":[["BrowserFlags",["Json.Decode.Value"]],["PreRenderFlags",[]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Form","comment":" `elm-pages` has a built-in integration with [`dillonkearns/elm-form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/). See the `dillonkearns/elm-form`\ndocs and examples for more information on how to define your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form). This module is the interface for rendering your `Form` in your `elm-pages` app.\n\nBy rendering your `Form` with this module,\nyou get all of the boilerplate managed for you automatically by the `elm-pages` framework. That means you do not need to use `Form.init`, `Form.update`, `Form.Model` since these are all\nabstracted away. In addition to that, in-flight form state is automatically managed for you and exposed through the `app` argument in your Route modules.\n\nThis means that you can declaratively derive Pending UI or Optimistic UI state from `app.navigation` or `app.concurrentSubmissions` in your Route modules, and even build a\nrich dynamic page that shows pending submissions in the UI without using your Route module's `Model`! This is the power of this abstraction - it's less error-prone to\ndeclaratively derive state rather than imperatively managing your `Model`.\n\n\n## Rendering Forms\n\n@docs renderHtml, renderStyledHtml\n\n@docs Options\n\n\n## Form Submission Strategies\n\nWhen you render with [`Pages.Form.renderHtml`](#renderHtml) or [`Pages.Form.renderStyledHtml`](#renderStyledHtml),\n`elm-pages` progressively enhances form submissions to manage the requests through Elm (instead of as a vanilla HTML form submission, which performs a full page reload).\n\nBy default, `elm-pages` Forms will use the same mental model as the browser's default form submission behavior. That is, the form submission state will be tied to the page's navigation state.\nIf you click a link while a form is submitting, the form submission will be cancelled and the page will navigate to the new page. Conceptually, you can think of this as being tied to the navigation state.\nA form submission is part of the page's navigation state, and so is a page navigation. So if you have a page with an edit form, a delete form (no inputs but only a delete button), and a link to a new page,\nyou can interact with any of these and it will cancel the previous interactions.\n\nYou can access this state through `app.navigation` in your `Route` module, which is a value of type `Pages.Navigation`.\n\nThis default form submission strategy is a good fit for more linear actions. This is more traditional server submission behavior that you might be familiar with from Rails or other server frameworks without JavaScript enhancement.\n\n@docs withConcurrent\n\n\n## Server-Side Validation\n\n@docs FormWithServerValidations, Handler\n\n","unions":[],"aliases":[{"name":"FormWithServerValidations","comment":" ","args":["error","combined","input","view"],"type":"Form.Form error { combine : Form.Validation.Validation error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) Basics.Never Basics.Never, view : Form.Context error input -> view } (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) input"},{"name":"Handler","comment":" ","args":["error","combined"],"type":"Form.Handler.Handler error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never))"},{"name":"Options","comment":" A replacement for [`Form.Options`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Options)\nwith some extra configuration for the `elm-pages` integration. You can use this type to annotate your form's options.\n","args":["error","parsed","input","msg"],"type":"Form.Options error parsed input msg { concurrent : Basics.Bool }"}],"values":[{"name":"renderHtml","comment":" A replacement for `Form.renderHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `elm/html` `Html`.\n","type":"List.List (Html.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Html (PagesMsg.PagesMsg userMsg)"},{"name":"renderStyledHtml","comment":" A replacement for `Form.renderStyledHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `rtfeldman/elm-css` `Html.Styled.Html`.\n","type":"List.List (Html.Styled.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Styled.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Styled.Html (PagesMsg.PagesMsg userMsg)"},{"name":"withConcurrent","comment":" Instead of using the default submission strategy (tied to the page's navigation state), you can use `withConcurrent`.\n`withConcurrent` allows multiple form submissions to be in flight at the same time. It is useful for more dynamic applications. A good rule of thumb\nis if you could have multiple pending spinners on the page at the same time, you should use `withConcurrent`. For example, if you have a page with a list of items,\nsay a Twitter clone. If you click the like button on a Tweet, it won't result in a page navigation. You can click the like button on multiple Tweets at the same time\nand they will all submit independently.\n\nIn the case of Twitter, there isn't an indication of a loading spinner on the like button because it is expected that it will succeed. This is an example of a User Experience (UX) pattern\ncalled Optimistic UI. Since it is very likely that liking a Tweet will be successful, the UI will update the UI as if it has immediately succeeded even though the request is still in flight.\nIf the request fails, the UI will be updated to reflect the failure with an animation to show that something went wrong.\n\nThe `withConcurrent` is a good fit for either of these UX patterns (Optimistic UI or Pending UI, i.e. showing a loading spinner). You can derive either of these\nvisual states from the `app.concurrentSubmissions` field in your `Route` module.\n\nYou can call `withConcurrent` on your `Form.Options`. Note that while `withConcurrent` will allow multiple form submissions to be in flight at the same time independently,\nthe ID of the Form will still have a unique submission. For example, if you click submit on a form with the ID `\"edit-123\"` and then submit it again before the first submission has completed,\nthe second submission will cancel the first submission. So it is important to use unique IDs for forms that represent unique operations.\n\n    import Form\n    import Pages.Form\n\n    todoItemView app todo =\n        deleteItemForm\n            |> Pages.Form.renderHtml []\n                (Form.options (\"delete-\" ++ todo.id)\n                    |> Form.withInput todo\n                    |> Pages.Form.withConcurrent\n                )\n                app\n\n","type":"Pages.Form.Options error parsed input msg -> Pages.Form.Options error parsed input msg"}],"binops":[]},{"name":"Pages.FormData","comment":"\n\n@docs FormData\n\n","unions":[],"aliases":[{"name":"FormData","comment":" The payload for form submissions.\n","args":[],"type":"{ fields : List.List ( String.String, String.String ), method : Form.Method, action : String.String, id : Maybe.Maybe String.String }"}],"values":[],"binops":[]},{"name":"Pages.Internal.DbRequest","comment":" Exposed for internal use only (used in generated code).\n\nBacks the elm-pages CLI-generated `Pages.Db` and `MigrateChain` modules.\nEach function targets exactly one `elm-pages-internal://db-*` runtime\nhandler — the endpoint name is hardcoded so callers cannot pivot to\nnon-DB internal routes.\n\n@docs readMeta, write\n@docs lockAcquire, lockRelease\n@docs migrateRead, migrateWrite\n\n","unions":[],"aliases":[],"values":[{"name":"lockAcquire","comment":" Acquire the write lock for a connection. Targets\n`elm-pages-internal://db-lock-acquire`.\n","type":"{ body : BackendTask.Http.Body, decoder : Json.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"lockRelease","comment":" Release a previously-acquired write lock. Targets\n`elm-pages-internal://db-lock-release`.\n","type":"{ body : BackendTask.Http.Body, decoder : Json.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"migrateRead","comment":" Read raw db.bin during a migration script. Targets\n`elm-pages-internal://db-migrate-read`.\n","type":"{ body : BackendTask.Http.Body, decoder : Bytes.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"migrateWrite","comment":" Write a migrated db.bin. Targets\n`elm-pages-internal://db-migrate-write`. Used both by `Pages.Db` (with\nconnection headers) and by `MigrateChain` (with no headers).\n","type":"{ headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, decoder : Json.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"readMeta","comment":" Read the on-disk db.bin metadata + payload for a connection. Targets\n`elm-pages-internal://db-read-meta`.\n","type":"{ body : BackendTask.Http.Body, decoder : Bytes.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"write","comment":" Write a new db.bin payload. Targets `elm-pages-internal://db-write`.\n","type":"{ headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, decoder : Json.Decode.Decoder a } -> BackendTask.BackendTask FatalError.FatalError a"}],"binops":[]},{"name":"Pages.Internal.NotFoundReason","comment":" Exposed for internal use only (used in generated code).\n\n@docs ModuleContext, NotFoundReason, Payload, Record, document\n\n","unions":[{"name":"NotFoundReason","comment":" ","args":[],"cases":[["NoMatchingRoute",[]],["NotPrerendered",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["NotPrerenderedOrHandledByFallback",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["UnhandledServerRoute",["Pages.Internal.NotFoundReason.ModuleContext"]]]}],"aliases":[{"name":"ModuleContext","comment":" ","args":[],"type":"{ moduleName : List.List String.String, routePattern : Pages.Internal.RoutePattern.RoutePattern, matchedRouteParams : Pages.Internal.NotFoundReason.Record }"},{"name":"Payload","comment":" ","args":[],"type":"{ path : UrlPath.UrlPath, reason : Pages.Internal.NotFoundReason.NotFoundReason }"},{"name":"Record","comment":" ","args":[],"type":"List.List ( String.String, String.String )"}],"values":[{"name":"document","comment":" ","type":"List.List Pages.Internal.RoutePattern.RoutePattern -> Pages.Internal.NotFoundReason.Payload -> { title : String.String, body : List.List (Html.Html msg) }"}],"binops":[]},{"name":"Pages.Internal.Platform","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, ProgramConfig, application, init, update\n\n@docs Effect, RequestInfo, view, ActionDataOrRedirect\n\n","unions":[{"name":"ActionDataOrRedirect","comment":" Internal representation of an action result used while processing\nfetcher submissions. A fetcher either produced action data to merge into\nthe current page state, or it triggered a redirect to another URL.\n","args":["action"],"cases":[["ActionResponse",["Maybe.Maybe action"]],["RedirectResponse",["String.String"]]]},{"name":"Effect","comment":" ","args":["userMsg","pageData","actionData","sharedData","userEffect","errorPage"],"cases":[["ScrollToTop",[]],["NoEffect",[]],["BrowserLoadUrl",["String.String"]],["BrowserPushUrl",["String.String"]],["BrowserReplaceUrl",["String.String"]],["Submit",["Pages.Internal.Platform.FormData"]],["SubmitFetcher",["String.String","Basics.Int","Pages.Internal.Platform.FormData"]],["Batch",["List.List (Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage)"]],["UserCmd",["userEffect"]],["CancelRequest",["Basics.Int"]],["RunCmd",["Platform.Cmd.Cmd (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]],["FetchFrozenViews",["{ path : String.String, query : Maybe.Maybe String.String, body : Maybe.Maybe String.String }"]]]},{"name":"Msg","comment":" ","args":["userMsg","pageData","actionData","sharedData","errorPage"],"cases":[["LinkClicked",["Browser.UrlRequest"]],["UrlChanged",["Url.Url"]],["UserMsg",["PagesMsg.PagesMsg userMsg"]],["FormMsg",["Form.Msg (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]],["UpdateCacheAndUrlNew",["Basics.Bool","Url.Url","Maybe.Maybe userMsg","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )"]],["FetcherComplete",["Basics.Bool","String.String","Basics.Int","Result.Result Http.Error ( Maybe.Maybe userMsg, Pages.Internal.Platform.ActionDataOrRedirect actionData )"]],["FetcherStarted",["String.String","Basics.Int","Pages.Internal.Platform.FormData","Time.Posix"]],["PageScrollComplete",[]],["HotReloadCompleteNew",["Bytes.Bytes"]],["ProcessFetchResponse",["Basics.Int","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]],["FrozenViewsReady",["Maybe.Maybe Bytes.Bytes"]],["NoOp",[]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["userModel","pageData","actionData","sharedData"],"type":"{ key : Maybe.Maybe Browser.Navigation.Key, url : Url.Url, currentPath : String.String, ariaNavigationAnnouncement : String.String, pageData : Result.Result String.String { userModel : userModel, pageData : pageData, sharedData : sharedData, actionData : Maybe.Maybe actionData }, notFound : Maybe.Maybe { reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }, userFlags : Json.Decode.Value, transition : Maybe.Maybe ( Basics.Int, Pages.Navigation.Navigation ), nextTransitionKey : Basics.Int, inFlightFetchers : Dict.Dict String.String ( Basics.Int, Pages.ConcurrentSubmission.ConcurrentSubmission actionData ), pageFormState : Form.Model, pendingRedirect : Basics.Bool, pendingData : Maybe.Maybe ( pageData, sharedData, Maybe.Maybe actionData ), pendingFrozenViewsUrl : Maybe.Maybe Url.Url }"},{"name":"Program","comment":" ","args":["userModel","userMsg","pageData","actionData","sharedData","errorPage"],"type":"Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"ProgramConfig","comment":" ","args":["userMsg","userModel","route","pageData","actionData","sharedData","effect","mappedMsg","errorPage"],"type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage"},{"name":"RequestInfo","comment":" ","args":[],"type":"{ contentType : String.String, body : String.String }"}],"values":[{"name":"application","comment":" ","type":"Pages.Internal.Platform.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"init","comment":" ","type":"Pages.Internal.Platform.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Flags -> Url.Url -> Maybe.Maybe Browser.Navigation.Key -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"view","comment":" ","type":"Pages.Internal.Platform.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> Browser.Document (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"}],"binops":[]},{"name":"Pages.Internal.Platform.Cli","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, ProgramConfig, Program, cliApplication, init, requestDecoder, update, currentCompatibilityKey\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["List.List { key : String.String, json : Json.Decode.Value, bytes : Maybe.Maybe Bytes.Bytes }"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["route"],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError Pages.Internal.Platform.Effect.Effect, errors : List.List BuildError.BuildError, maybeRequestJson : RenderRequest.RenderRequest route, isDevServer : Basics.Bool }"},{"name":"Program","comment":" ","args":["route"],"type":"Platform.Program Pages.Internal.Platform.Cli.Flags (Pages.Internal.Platform.Cli.Model route) Pages.Internal.Platform.Cli.Msg"},{"name":"ProgramConfig","comment":" ","args":["userMsg","userModel","route","pageData","actionData","sharedData","effect","mappedMsg","errorPage"],"type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage"}],"values":[{"name":"cliApplication","comment":" ","type":"Pages.Internal.Platform.Cli.ProgramConfig userMsg userModel (Maybe.Maybe route) pageData actionData sharedData effect mappedMsg errorPage -> Pages.Internal.Platform.Cli.Program (Maybe.Maybe route)"},{"name":"currentCompatibilityKey","comment":" ","type":"Basics.Int"},{"name":"init","comment":" ","type":"Pages.SiteConfig.SiteConfig -> RenderRequest.RenderRequest route -> Pages.Internal.Platform.Cli.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage -> Json.Decode.Value -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.Cli.Msg -> Pages.Internal.Platform.Cli.Model route -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.Platform.GeneratorApplication","comment":" Exposed for internal use only (used in generated code).\n\n@docs Program, Flags, Model, Msg, init, requestDecoder, update, app, JsonValue\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["List.List { key : String.String, json : Json.Decode.Value, bytes : Maybe.Maybe Bytes.Bytes }"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"{ compatibilityKey : Basics.Int }"},{"name":"JsonValue","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":[],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError (), errors : List.List BuildError.BuildError }"},{"name":"Program","comment":" ","args":[],"type":"Cli.Program.StatefulProgram Pages.Internal.Platform.GeneratorApplication.Model Pages.Internal.Platform.GeneratorApplication.Msg (BackendTask.BackendTask FatalError.FatalError ()) Pages.Internal.Platform.GeneratorApplication.Flags"}],"values":[{"name":"app","comment":" ","type":"Pages.GeneratorProgramConfig.GeneratorProgramConfig -> Pages.Internal.Platform.GeneratorApplication.Program"},{"name":"init","comment":" ","type":"BackendTask.BackendTask FatalError.FatalError () -> Cli.Program.FlagsIncludingArgv Pages.Internal.Platform.GeneratorApplication.Flags -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.GeneratorApplication.Msg -> Pages.Internal.Platform.GeneratorApplication.Model -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.ResponseSketch","comment":"\n\n@docs ResponseSketch\n\n","unions":[{"name":"ResponseSketch","comment":" ","args":["data","action","shared"],"cases":[["RenderPage",["data","Maybe.Maybe action"]],["HotUpdate",["data","shared","Maybe.Maybe action"]],["Redirect",["String.String"]],["NotFound",["{ reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }"]],["Action",["action"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Internal.RoutePattern","comment":" Exposed for internal use only (used in generated code).\n\n@docs Ending, RoutePattern, Segment, view, toVariant, routeToBranch\n\n@docs Param, RouteParam, fromModuleName, hasRouteParams, repeatWithoutOptionalEnding, toModuleName, toRouteParamTypes, toRouteParamsRecord, toVariantName\n\n","unions":[{"name":"Ending","comment":" ","args":[],"cases":[["Optional",["String.String"]],["RequiredSplat",[]],["OptionalSplat",[]]]},{"name":"Param","comment":" ","args":[],"cases":[["RequiredParam",[]],["OptionalParam",[]],["RequiredSplatParam",[]],["OptionalSplatParam",[]]]},{"name":"RouteParam","comment":" ","args":[],"cases":[["StaticParam",["String.String"]],["DynamicParam",["String.String"]],["OptionalParam2",["String.String"]],["RequiredSplatParam2",[]],["OptionalSplatParam2",[]]]},{"name":"Segment","comment":" ","args":[],"cases":[["StaticSegment",["String.String"]],["DynamicSegment",["String.String"]]]}],"aliases":[{"name":"RoutePattern","comment":" ","args":[],"type":"{ segments : List.List Pages.Internal.RoutePattern.Segment, ending : Maybe.Maybe Pages.Internal.RoutePattern.Ending }"}],"values":[{"name":"fromModuleName","comment":" ","type":"List.List String.String -> Maybe.Maybe Pages.Internal.RoutePattern.RoutePattern"},{"name":"hasRouteParams","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Basics.Bool"},{"name":"repeatWithoutOptionalEnding","comment":" ","type":"List.List Pages.Internal.RoutePattern.RouteParam -> Maybe.Maybe (List.List Pages.Internal.RoutePattern.RouteParam)"},{"name":"routeToBranch","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( Elm.CodeGen.Pattern, Elm.CodeGen.Expression )"},{"name":"toModuleName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List String.String"},{"name":"toRouteParamTypes","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Pages.Internal.RoutePattern.Param )"},{"name":"toRouteParamsRecord","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Elm.Annotation.Annotation )"},{"name":"toVariant","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Elm.Variant"},{"name":"toVariantName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> { variantName : String.String, params : List.List Pages.Internal.RoutePattern.RouteParam }"},{"name":"view","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Html.Html msg"}],"binops":[]},{"name":"Pages.Internal.Router","comment":" Exposed for internal use only (used in generated code).\n\n@docs Matcher, firstMatch, fromOptionalSplat, maybeToList, nonEmptyToList, toNonEmpty\n\n","unions":[],"aliases":[{"name":"Matcher","comment":" ","args":["route"],"type":"{ pattern : String.String, toRoute : List.List (Maybe.Maybe String.String) -> Maybe.Maybe route }"}],"values":[{"name":"firstMatch","comment":" ","type":"List.List (Pages.Internal.Router.Matcher route) -> String.String -> Maybe.Maybe route"},{"name":"fromOptionalSplat","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"maybeToList","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"nonEmptyToList","comment":" ","type":"( String.String, List.List String.String ) -> List.List String.String"},{"name":"toNonEmpty","comment":" ","type":"String.String -> ( String.String, List.List String.String )"}],"binops":[]},{"name":"Pages.Manifest","comment":" Represents the configuration of a\n[web manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest).\n\nYou pass your `Pages.Manifest.Config` record into the `Pages.Manifest.generator`\nin your `app/Api.elm` module to define a file generator that will build a `manifest.json` file as part of your build.\n\n    import Pages.Manifest as Manifest\n    import Pages.Manifest.Category\n\n    manifest : Manifest.Config\n    manifest =\n        Manifest.init\n            { name = static.siteName\n            , description = \"elm-pages - \" ++ tagline\n            , startUrl = Route.Index {} |> Route.toPath\n            , icons =\n                [ icon webp 192\n                , icon webp 512\n                , icon MimeType.Png 192\n                , icon MimeType.Png 512\n                ]\n            }\n            |> Manifest.withShortName \"elm-pages\"\n\n@docs Config, Icon\n\n\n## Builder options\n\n@docs init\n\n@docs withBackgroundColor, withCategories, withDisplayMode, withIarcRatingId, withLang, withOrientation, withShortName, withThemeColor\n\n\n## Arbitrary Fields Escape Hatch\n\n@docs withField\n\n\n## Config options\n\n@docs DisplayMode, Orientation, IconPurpose\n\n\n## Generating a Manifest.json\n\n@docs generator\n\n\n## Functions for use by the generated code (`Pages.elm`)\n\n@docs toJson\n\n","unions":[{"name":"DisplayMode","comment":" See <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>\n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" <https://w3c.github.io/manifest/#dfn-icon-purposes>\n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>\n","args":[],"cases":[["Any",[]],["Natural",[]],["Landscape",[]],["LandscapePrimary",[]],["LandscapeSecondary",[]],["Portrait",[]],["PortraitPrimary",[]],["PortraitSecondary",[]]]}],"aliases":[{"name":"Config","comment":" Represents a [web app manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n(see above for how to use it).\n","args":[],"type":"{ backgroundColor : Maybe.Maybe Color.Color, categories : List.List Pages.Manifest.Category.Category, displayMode : Pages.Manifest.DisplayMode, orientation : Pages.Manifest.Orientation, description : String.String, iarcRatingId : Maybe.Maybe String.String, name : String.String, themeColor : Maybe.Maybe Color.Color, startUrl : UrlPath.UrlPath, shortName : Maybe.Maybe String.String, icons : List.List Pages.Manifest.Icon, lang : LanguageTag.LanguageTag, otherFields : Dict.Dict String.String Json.Encode.Value }"},{"name":"Icon","comment":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/icons>\n","args":[],"type":"{ src : Pages.Url.Url, sizes : List.List ( Basics.Int, Basics.Int ), mimeType : Maybe.Maybe MimeType.MimeImage, purposes : List.List Pages.Manifest.IconPurpose }"}],"values":[{"name":"generator","comment":" A generator for `Api.elm` to include a manifest.json. The String argument is the canonical URL of the site.\n\n    module Api exposing (routes)\n\n    import ApiRoute\n    import Pages.Manifest\n\n    routes :\n        BackendTask FatalError (List Route)\n        -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n        -> List (ApiRoute.ApiRoute ApiRoute.Response)\n    routes getStaticRoutes htmlToString =\n        [ Pages.Manifest.generator\n            Site.canonicalUrl\n            Manifest.config\n        ]\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError Pages.Manifest.Config -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"init","comment":" Setup a minimal Manifest.Config. You can then use the `with...` builder functions to set additional options.\n","type":"{ description : String.String, name : String.String, startUrl : UrlPath.UrlPath, icons : List.List Pages.Manifest.Icon } -> Pages.Manifest.Config"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> Pages.Manifest.Config -> Json.Encode.Value"},{"name":"withBackgroundColor","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/background_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>.\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>.\n","type":"Pages.Manifest.DisplayMode -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withField","comment":" Escape hatch for specifying fields that aren't exposed through this module otherwise. The possible supported properties\nin a manifest file can change over time, so see [MDN manifest.json docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json)\nfor a full listing of the current supported properties.\n","type":"String.String -> Json.Encode.Value -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withIarcRatingId","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/iarc_rating_id>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/lang>.\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>.\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/short_name>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See <https://github.com/w3c/manifest/wiki/Categories> and\n<https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>\n\n@docs toString, Category\n\n@docs books, business, education, entertainment, finance, fitness, food, games, government, health, kids, lifestyle, magazines, medical, music, navigation, news, personalization, photo, politics, productivity, security, shopping, social, sports, travel, utilities, weather\n\n\n## Custom categories\n\n@docs custom\n\n","unions":[{"name":"Category","comment":" Represents a known, valid category, as specified by\n<https://github.com/w3c/manifest/wiki/Categories>. If this document is updated\nand I don't add it, please open an issue or pull request to let me know!\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"books","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"business","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"custom","comment":" It's best to use the pre-defined categories to ensure that clients (Android, iOS,\nChrome, Windows app store, etc.) are aware of it and can handle it appropriately.\nBut, if you're confident about using a custom one, you can do so with `Pages.Manifest.custom`.\n","type":"String.String -> Pages.Manifest.Category.Category"},{"name":"education","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"entertainment","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"finance","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"fitness","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"food","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"games","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"government","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"health","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"kids","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"lifestyle","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"magazines","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"medical","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"music","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"navigation","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"news","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"personalization","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"photo","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"politics","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"productivity","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"security","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"shopping","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"social","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"sports","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"toString","comment":" Turn a category into its official String representation, as seen\nhere: <https://github.com/w3c/manifest/wiki/Categories>.\n","type":"Pages.Manifest.Category.Category -> String.String"},{"name":"travel","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"utilities","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"weather","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"}],"binops":[]},{"name":"Pages.Navigation","comment":" `elm-pages` maintains a single `Maybe Navigation` state which is accessible from your `Route` modules through `app.navigation`.\n\nYou can use it to show a loading indicator while a page is loading:\n\n    import Pages.Navigation as Navigation\n\n    pageLoadingIndicator app =\n        case app.navigation of\n            Just (Navigation.Loading path _) ->\n                Spinner.view\n\n            Nothing ->\n                emptyView\n\n    emptyView : Html msg\n    emptyView =\n        Html.text \"\"\n\nYou can also use it to derive Pending UI or Optimistic UI from a pending form submission:\n\n    import Form\n    import Form.Handler\n    import Pages.Navigation as Navigation\n\n    view app =\n        let\n            optimisticProduct : Maybe Product\n            optimisticProduct =\n                case app.navigation of\n                    Just (Navigation.Submitting formData) ->\n                        formHandler\n                            |> Form.Handler.run formData\n                            |> Form.toResult\n                            |> Result.toMaybe\n\n                    Just (Navigation.LoadAfterSubmit formData path _) ->\n                        formHandler\n                            |> Form.Handler.run formData\n                            |> Form.toResult\n                            |> Result.toMaybe\n\n                    Nothing ->\n                        Nothing\n        in\n        -- our `productsView` function could show a loading spinner (Pending UI),\n        -- or it could assume the product will be created successfully (Optimistic UI) and\n        -- display it as a regular product in the list\n        productsView optimisticProduct app.data.products\n\n    allForms : Form.Handler.Handler String Product\n    allForms =\n        Form.Handler.init identity productForm\n\n    editItemForm : Form.HtmlForm String Product input msg\n    editItemForm =\n        Debug.todo \"Form definition here\"\n\n@docs Navigation, LoadingState\n\n","unions":[{"name":"LoadingState","comment":" ","args":[],"cases":[["Redirecting",[]],["Load",[]],["ActionRedirect",[]]]},{"name":"Navigation","comment":" Represents the global page navigation state of the app.\n\n  - `Loading` - navigating to a page, for example from a link click, or from a programmatic navigation with `Browser.Navigation.pushUrl`.\n  - `Submitting` - submitting a form using the default submission strategy (note that Forms rendered with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) Option have their state managed in `app.concurrentSubmissions` instead of `app.navigation`).\n  - `LoadAfterSubmit` - the state immediately after `Submitting` - allows you to continue using the `FormData` from a submission while a data reload or redirect is occurring.\n\n","args":[],"cases":[["Submitting",["Pages.FormData.FormData"]],["LoadAfterSubmit",["Pages.FormData.FormData","UrlPath.UrlPath","Pages.Navigation.LoadingState"]],["Loading",["UrlPath.UrlPath","Pages.Navigation.LoadingState"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.PageUrl","comment":" Same as a Url in `elm/url`, but slightly more structured. The path portion of the URL is parsed into a `List String` representing each segment, and\nthe query params are parsed into a `Dict String (List String)`.\n\n@docs PageUrl, toUrl\n\n@docs parseQueryParams\n\n","unions":[],"aliases":[{"name":"PageUrl","comment":" ","args":[],"type":"{ protocol : Url.Protocol, host : String.String, port_ : Maybe.Maybe Basics.Int, path : UrlPath.UrlPath, query : Dict.Dict String.String (List.List String.String), fragment : Maybe.Maybe String.String }"}],"values":[{"name":"parseQueryParams","comment":" ","type":"String.String -> Dict.Dict String.String (List.List String.String)"},{"name":"toUrl","comment":" ","type":"Pages.PageUrl.PageUrl -> Url.Url"}],"binops":[]},{"name":"Pages.Script","comment":" An elm-pages Script is a way to execute an `elm-pages` `BackendTask`.\n\nRead more about using the `elm-pages` CLI to run (or bundle) scripts, plus a brief tutorial, at <https://elm-pages.com/docs/elm-pages-scripts>.\n\n@docs Script\n\n\n## Defining Scripts\n\n@docs withCliOptions, withoutCliOptions, withSchema, metadata, withDatabasePath\n\n\n## File System Utilities\n\n@docs writeFile, removeFile, copyFile, move\n\n@docs makeDirectory, removeDirectory, makeTempDirectory\n\n\n## Shell Commands\n\n@docs command, exec\n\n\n## Utilities\n\n@docs log, sleep, doThen, which, expectWhich, question, readKey, readKeyWithDefault\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" The recoverable error type for file writes. You can use `BackendTask.allowFatal` if you want to allow the program to crash\nwith an error message if a file write is unsuccessful.\n","args":[],"cases":[["FileWriteError",[]]]}],"aliases":[{"name":"Script","comment":" The type for your `run` function that can be executed by `elm-pages run`.\n","args":[],"type":"Pages.Internal.Script.Script"}],"values":[{"name":"command","comment":" Run a single command and return stderr and stdout combined as a single String.\n\nIf you want to do more advanced things like piping together multiple commands in a pipeline, or piping in a file to a command, etc., see the `BackendTask.Stream` module.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Script.command \"ls\" []\n                |> BackendTask.andThen\n                    (\\files ->\n                        Script.log (\"Files: \" ++ files)\n                    )\n            )\n\n","type":"String.String -> List.List String.String -> BackendTask.BackendTask FatalError.FatalError String.String"},{"name":"copyFile","comment":" Copy a single file. Auto-creates parent directories of the destination (matching `writeFile` behavior).\n\n    Script.copyFile { from = \"src/config.json\", to = \"dist/config.json\" }\n\n","type":"{ from : String.String, to : String.String } -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"doThen","comment":" Run a command with no output, then run another command.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run =\n        Script.withoutCliOptions\n            (Script.log \"Hello!\"\n                |> Script.doThen\n                    (Script.log \"World!\")\n            )\n\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error () -> BackendTask.BackendTask error value"},{"name":"exec","comment":" Like [`command`](#command), but prints stderr and stdout to the console as the command runs instead of capturing them.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script exposing (Script)\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Script.exec \"ls\" [])\n\n","type":"String.String -> List.List String.String -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"expectWhich","comment":" Check if a command is available on the system. If it is, return the full path to the command, otherwise fail with a `FatalError`.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Script.expectWhich \"elm-review\"\n                |> BackendTask.andThen\n                    (\\path ->\n                        Script.log (\"The path to `elm-review` is: \" ++ path)\n                    )\n            )\n\nIf you run it with a command that is not available, you will see an error like this:\n\n    Script.expectWhich \"hype-script\"\n\n```shell\n-- COMMAND NOT FOUND ---------------\nI expected to find `hype-script`, but it was not on your PATH. Make sure it is installed and included in your PATH.\n```\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError String.String"},{"name":"log","comment":" Log to stdout.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run =\n        Script.withoutCliOptions\n            (Script.log \"Hello!\"\n                |> BackendTask.allowFatal\n            )\n\n","type":"String.String -> BackendTask.BackendTask error ()"},{"name":"makeDirectory","comment":" Create a directory.\n\nThe `{ recursive : Bool }` flag controls whether parent directories are created (like `mkdir -p`).\n\n    -- Create nested directories\n    Script.makeDirectory { recursive = True } \"dist/assets/images\"\n\n    -- Create a single directory (parent must exist)\n    Script.makeDirectory { recursive = False } \"output\"\n\n","type":"{ recursive : Basics.Bool } -> String.String -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"makeTempDirectory","comment":" Create a temporary directory with a given prefix. Returns the absolute path to the created directory.\n\nPairs naturally with `BackendTask.finally` for cleanup:\n\n    Script.makeTempDirectory \"my-build-\"\n        |> BackendTask.andThen\n            (\\tmpDir ->\n                doWork tmpDir\n                    |> BackendTask.finally\n                        (Script.removeDirectory { recursive = True } tmpDir)\n            )\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError String.String"},{"name":"metadata","comment":" For internal use by generated code. Most users should not need this.\n","type":"{ moduleName : String.String, path : String.String } -> Pages.Script.Script -> Maybe.Maybe Json.Encode.Value"},{"name":"move","comment":" Move (rename) a file or directory. Atomic on the same filesystem. Auto-creates parent directories of the destination.\n\n    Script.move { from = \"build/output.js\", to = \"dist/app.js\" }\n\n","type":"{ from : String.String, to : String.String } -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"question","comment":"\n\n    module QuestionDemo exposing (run)\n\n    import BackendTask\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Script.question \"What is your name? \"\n                |> BackendTask.andThen\n                    (\\name ->\n                        Script.log (\"Hello, \" ++ name ++ \"!\")\n                    )\n            )\n\n","type":"String.String -> BackendTask.BackendTask error String.String"},{"name":"readKey","comment":" Read a single keypress from stdin without requiring Enter.\n\nThis is useful for interactive prompts where you want immediate response\nto a single key, like confirmation dialogs (y/n) or menu navigation.\n\n    module ConfirmDemo exposing (run)\n\n    import BackendTask\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Script.log \"Approve this change? [y/n] \"\n                |> BackendTask.andThen (\\_ -> Script.readKey)\n                |> BackendTask.andThen\n                    (\\key ->\n                        if key == \"y\" then\n                            Script.log \"Approved!\"\n\n                        else\n                            Script.log \"Rejected.\"\n                    )\n            )\n\nNote: Returns the raw key character. Control characters like Ctrl+C will\nterminate the process.\n\nWhen not running in an interactive terminal (e.g., piped input or CI),\nfalls back to line-buffered input and returns the first character of the line.\nThis allows scripts to work both interactively and with piped input like\n`echo \"y\" | elm-pages run MyScript.elm`.\n\n","type":"BackendTask.BackendTask error String.String"},{"name":"readKeyWithDefault","comment":" Like [`readKey`](#readKey), but returns a default value when Enter is pressed.\n\n    Script.log \"Continue? [Y/n] \"\n        |> BackendTask.andThen (\\_ -> Script.readKeyWithDefault \"y\")\n        |> BackendTask.andThen\n            (\\key ->\n                if String.toLower key == \"y\" then\n                    continue\n\n                else\n                    abort\n            )\n\nUseful for prompts where pressing Enter should accept a default option.\n\n","type":"String.String -> BackendTask.BackendTask error String.String"},{"name":"removeDirectory","comment":" Remove a directory. Silently succeeds if the directory doesn't exist (like `rm -f` on a missing path).\n\nThe `{ recursive : Bool }` flag only controls whether non-empty directories can be removed (`rm -r` behavior).\nIt does not control force semantics.\n\n    -- Remove a directory and all its contents\n    Script.removeDirectory { recursive = True } \"build\"\n\n    -- Remove only if empty\n    Script.removeDirectory { recursive = False } \"empty-dir\"\n\n","type":"{ recursive : Basics.Bool } -> String.String -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"removeFile","comment":" Remove a file. Silently succeeds if the file doesn't exist (like `rm -f`).\n\n    Script.writeFile { path = \"temp.txt\", body = \"...\" }\n        |> BackendTask.allowFatal\n        |> BackendTask.andThen (\\_ -> Script.removeFile \"temp.txt\")\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"sleep","comment":" Sleep for a number of milliseconds.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run =\n        Script.withoutCliOptions\n            (Script.log \"Hello...\"\n                |> Script.doThen\n                    (Script.sleep 1000)\n                |> Script.doThen\n                    (Script.log \"World!\")\n            )\n\n","type":"Basics.Int -> BackendTask.BackendTask error ()"},{"name":"which","comment":" Same as [`expectWhich`](#expectWhich), but returns `Nothing` if the command is not found instead of failing with a `FatalError`.\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"},{"name":"withCliOptions","comment":" Same as [`withoutCliOptions`](#withoutCliOptions), but allows you to define a CLI Options Parser so the user can\npass in additional options for the script.\n\nUses <https://package.elm-lang.org/packages/dillonkearns/elm-cli-options-parser/latest/>.\n\nRead more at <https://elm-pages.com/docs/elm-pages-scripts/#adding-command-line-options>.\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> BackendTask.BackendTask FatalError.FatalError ()) -> Pages.Script.Script"},{"name":"withDatabasePath","comment":" Configure the default database file path for `Pages.Db.default` in this script run.\n\nUse this when you want the shared default connection path.\nFor explicit connection-based paths (for example from CLI options),\nuse `Pages.Db.open`.\n\n    import Pages.Db\n    import Pages.Script as Script\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Pages.Db.update Pages.Db.default (\\db -> db)\n                |> BackendTask.allowFatal\n            )\n            |> Script.withDatabasePath \".elm-pages-data/prefs.db.bin\"\n\n","type":"String.String -> Pages.Script.Script -> Pages.Script.Script"},{"name":"withSchema","comment":" Like [`withCliOptions`](#withCliOptions), but with a typed output schema.\n\nThe return value of your `run` function is automatically JSON-encoded and\nprinted to stdout using the provided encoder. Running with `--introspect-cli`\nshort-circuits execution and prints metadata including the script's\n`inputSchema` and output JSON Schema instead, so that tools and LLM agents\ncan discover how to call the script and what it returns without running it.\n\nThe schema is derived from the same `Encoder` that does the actual encoding,\nso it can never drift out of sync with the real output.\n\n`description` is a short summary of what the script does. It appears in\n`--introspect-cli` output so that tools can decide whether to call it. The\n`help` field in the output is the usage synopsis from `--help` (without\nANSI colors). Scripts defined with `withSchema` are also automatically\nincluded in `elm-pages introspect`; you do not need to expose any extra\ntop-level values for that to work. To invoke a script with JSON input\ndirectly, pass the JSON object as a single shell argument.\n\nExample: a script that checks whether a URL is reachable.\n\n    -- file: script/src/CheckStatus.elm\n    run : Script\n    run =\n        Script.withSchema\n            { description =\n                \"Check whether a URL is reachable\"\n            , cliOptions =\n                Program.config\n                    |> Program.add\n                        (OptionsParser.build identity\n                            |> OptionsParser.with\n                                (Option.requiredKeywordArg \"url\")\n                        )\n            , encoder =\n                TsEncode.object\n                    [ TsEncode.required \"reachable\"\n                        .reachable\n                        TsEncode.bool\n                    , TsEncode.required \"statusCode\"\n                        .statusCode\n                        TsEncode.int\n                    ]\n            , run = checkStatus\n            }\n\n    elm-pages run CheckStatus.elm --url https://example.com\n    # { \"reachable\": true, \"statusCode\": 200 }\n\n    elm-pages run CheckStatus.elm '{\"url\":\"https://example.com\",\"$cli\":{}}'\n    # { \"reachable\": true, \"statusCode\": 200 }\n\n    elm-pages run CheckStatus.elm --introspect-cli\n    # { \"name\": \"CheckStatus\",\n    #   \"description\": \"Check whether a URL is reachable\",\n    #   \"help\": \"CheckStatus --url <URL>\",\n    #   \"inputSchema\": { ... },\n    #   \"outputSchema\": { ... } }\n\n","type":"{ description : String.String, cliOptions : Cli.Program.Config cliFlags, encoder : TsJson.Encode.Encoder outputType, run : cliFlags -> BackendTask.BackendTask FatalError.FatalError outputType } -> Pages.Script.Script"},{"name":"withoutCliOptions","comment":" Define a simple Script (no CLI Options).\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run =\n        Script.withoutCliOptions\n            (Script.log \"Hello!\"\n                |> BackendTask.allowFatal\n            )\n\n","type":"BackendTask.BackendTask FatalError.FatalError () -> Pages.Script.Script"},{"name":"writeFile","comment":" Write a file to the file system.\n\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory), or you can pass in absolute paths beginning with a `/`.\n\n    module MyScript exposing (run)\n\n    import BackendTask\n    import Pages.Script as Script\n\n    run =\n        Script.withoutCliOptions\n            (Script.writeFile\n                { path = \"hello.json\"\n                , body = \"\"\"{ \"message\": \"Hello, World!\" }\"\"\"\n                }\n                |> BackendTask.allowFatal\n            )\n\n","type":"{ path : String.String, body : String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : Pages.Script.Error } ()"}],"binops":[]},{"name":"Pages.Script.Spinner","comment":"\n\n\n## Running Steps\n\nThe easiest way to use spinners is to define a series of [`Steps`](#Steps) and then run them with [`runSteps`](#runSteps).\n\nSteps are a sequential series of `BackendTask`s that are run one after the other. If a step fails (has a [`FatalError`](FatalError)),\nits spinner will show a failure, and the remaining steps will not be run and will be displayed as cancelled (the step name in gray).\n\n    module StepsDemo exposing (run)\n\n    import BackendTask exposing (BackendTask)\n    import Pages.Script as Script exposing (Script)\n    import Pages.Script.Spinner as Spinner\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (Spinner.steps\n                |> Spinner.withStep \"Compile Main.elm\" (\\() -> Script.exec \"elm\" [ \"make\", \"src/Main.elm\", \"--output=/dev/null\" ])\n                |> Spinner.withStep \"Verify formatting\" (\\() -> Script.exec \"elm-format\" [ \"--validate\", \"src/\" ])\n                |> Spinner.withStep \"elm-review\" (\\() -> Script.exec \"elm-review\" [])\n                |> Spinner.runSteps\n            )\n\n@docs Steps, steps, withStep\n\n@docs withStepWithOptions\n\n@docs runSteps\n\n\n## Configuring Steps\n\n@docs Options, options\n\n@docs CompletionIcon\n\n@docs withOnCompletion\n\n\n## Running with BackendTask\n\n@docs runTask, runTaskWithOptions\n\n\n## Low-Level\n\n@docs showStep, runSpinnerWithTask, Spinner\n\n","unions":[{"name":"CompletionIcon","comment":" An icon used to indicate the completion status of a step. Set by using [`withOnCompletion`](#withOnCompletion).\n","args":[],"cases":[["Succeed",[]],["Fail",[]],["Warn",[]],["Info",[]]]},{"name":"Options","comment":" Configuration that can be used with [`runTaskWithOptions`](#runTaskWithOptions) and [`withStepWithOptions`](#withStepWithOptions).\n","args":["error","value"],"cases":[]},{"name":"Spinner","comment":" ","args":["error","value"],"cases":[]},{"name":"Steps","comment":" The definition of a series of `BackendTask`s to run, with a spinner for each step.\n","args":["error","value"],"cases":[["Steps",["BackendTask.BackendTask error value"]]]}],"aliases":[],"values":[{"name":"options","comment":" The default options for a spinner. The spinner `text` is a required argument and will be displayed as the step name.\n\n    import Pages.Script.Spinner as Spinner\n\n    example =\n        Spinner.options \"Compile Main.elm\"\n\n","type":"String.String -> Pages.Script.Spinner.Options error value"},{"name":"runSpinnerWithTask","comment":" After calling `showStep` to get a reference to a `Spinner`, use `runSpinnerWithTask` to run a `BackendTask` and show a failure or success\ncompletion status once it is done.\n","type":"Pages.Script.Spinner.Spinner error value -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"runSteps","comment":" Perform the `Steps` in sequence.\n","type":"Pages.Script.Spinner.Steps FatalError.FatalError value -> BackendTask.BackendTask FatalError.FatalError value"},{"name":"runTask","comment":" Run a `BackendTask` with a spinner. The spinner will show a success icon if the task succeeds, and a failure icon if the task fails.\n\nIt's often easier to use [`steps`](#steps) when possible.\n\n        module SequentialSteps exposing (run)\n\n        import Pages.Script as Script exposing (Script, doThen, sleep)\n        import Pages.Script.Spinner as Spinner\n\n\n        run : Script\n        run =\n            Script.withoutCliOptions\n                (sleep 3000\n                    |> Spinner.runTask \"Step 1...\"\n                    |> doThen\n                        (sleep 3000\n                            |> Spinner.runTask \"Step 2...\"\n                            |> doThen\n                                (sleep 3000\n                                    |> Spinner.runTask \"Step 3...\"\n                                )\n                        )\n                )\n\n","type":"String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"runTaskWithOptions","comment":" ","type":"Pages.Script.Spinner.Options error value -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"showStep","comment":" `showStep` gives you a `Spinner` reference which you can use to start the spinner later with `runSpinnerWithTask`.\n\nMost use cases can be achieved more easily using more high-level helpers, like [`runTask`](#runTask) or [`steps`](#steps).\n`showStep` can be useful if you have more dynamic steps that you want to reveal over time.\n\n    module ShowStepDemo exposing (run)\n\n    import BackendTask exposing (BackendTask)\n    import Pages.Script as Script exposing (Script, doThen, sleep)\n    import Pages.Script.Spinner as Spinner\n\n    run : Script\n    run =\n        Script.withoutCliOptions\n            (BackendTask.succeed\n                (\\spinner1 spinner2 spinner3 ->\n                    sleep 3000\n                        |> Spinner.runSpinnerWithTask spinner1\n                        |> doThen\n                            (sleep 3000\n                                |> Spinner.runSpinnerWithTask spinner2\n                                |> doThen\n                                    (sleep 3000\n                                        |> Spinner.runSpinnerWithTask spinner3\n                                    )\n                            )\n                )\n                |> BackendTask.andMap\n                    (Spinner.options \"Step 1\" |> Spinner.showStep)\n                |> BackendTask.andMap\n                    (Spinner.options \"Step 2\" |> Spinner.showStep)\n                |> BackendTask.andMap\n                    (Spinner.options \"Step 3\" |> Spinner.showStep)\n                |> BackendTask.andThen identity\n            )\n\n","type":"Pages.Script.Spinner.Options error value -> BackendTask.BackendTask error (Pages.Script.Spinner.Spinner error value)"},{"name":"steps","comment":" Initialize an empty series of `Steps`.\n","type":"Pages.Script.Spinner.Steps FatalError.FatalError ()"},{"name":"withOnCompletion","comment":" Set the completion icon and text based on the result of the task.\n\n    import Pages.Script.Spinner as Spinner\n\n    example =\n        Spinner.options \"Fetching data\"\n            |> Spinner.withOnCompletion\n                (\\result ->\n                    case result of\n                        Ok _ ->\n                            ( Spinner.Succeed, \"Fetched data!\" )\n\n                        Err _ ->\n                            ( Spinner.Fail\n                            , Just \"Could not fetch data.\"\n                            )\n                )\n\n","type":"(Result.Result error value -> ( Pages.Script.Spinner.CompletionIcon, Maybe.Maybe String.String )) -> Pages.Script.Spinner.Options error value -> Pages.Script.Spinner.Options error value"},{"name":"withStep","comment":" Add a `Step`. See [`withStepWithOptions`](#withStepWithOptions) to configure the step's spinner.\n","type":"String.String -> (oldValue -> BackendTask.BackendTask FatalError.FatalError newValue) -> Pages.Script.Spinner.Steps FatalError.FatalError oldValue -> Pages.Script.Spinner.Steps FatalError.FatalError newValue"},{"name":"withStepWithOptions","comment":" Add a step with custom [`Options`](#Options).\n","type":"Pages.Script.Spinner.Options FatalError.FatalError newValue -> (oldValue -> BackendTask.BackendTask FatalError.FatalError newValue) -> Pages.Script.Spinner.Steps FatalError.FatalError oldValue -> Pages.Script.Spinner.Steps FatalError.FatalError newValue"}],"binops":[]},{"name":"Pages.Url","comment":" Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.\n\nThat's the purpose for this type. If you have an external URL, like `Pages.Url.external \"https://google.com\"`,\nthen the canonicalUrl will not be prepended when it is used in a head tag.\n\nIf you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`\n\n@docs Url, external, fromPath, toAbsoluteUrl, toString\n\n","unions":[{"name":"Url","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"external","comment":" ","type":"String.String -> Pages.Url.Url"},{"name":"fromPath","comment":" ","type":"UrlPath.UrlPath -> Pages.Url.Url"},{"name":"toAbsoluteUrl","comment":" ","type":"String.String -> Pages.Url.Url -> String.String"},{"name":"toString","comment":" ","type":"Pages.Url.Url -> String.String"}],"binops":[]},{"name":"PagesMsg","comment":" In `elm-pages`, Route modules have their own `Msg` type which can be used like a normal TEA (The Elm Architecture) app.\nBut the `Msg` defined in a `Route` module is wrapped in the `PagesMsg` type.\n\n@docs PagesMsg\n\nYou can wrap your Route Module's `Msg` using `fromMsg`.\n\n@docs fromMsg\n\n@docs map, noOp\n\n","unions":[],"aliases":[{"name":"PagesMsg","comment":" ","args":["userMsg"],"type":"Pages.Internal.Msg.Msg userMsg"}],"values":[{"name":"fromMsg","comment":"\n\n    import Form\n    import Pages.Form\n    import PagesMsg exposing (PagesMsg)\n\n    type Msg\n        = ToggleMenu\n\n    view :\n        Maybe PageUrl\n        -> Shared.Model\n        -> Model\n        -> App Data ActionData RouteParams\n        -> View (PagesMsg Msg)\n    view maybeUrl sharedModel model app =\n        { title = \"My Page\"\n        , view =\n            [ button\n                -- we need to wrap our Route module's `Msg` here so we have a `PagesMsg Msg`\n                [ onClick (PagesMsg.fromMsg ToggleMenu) ]\n                []\n\n            -- `Pages.Form.renderHtml` gives us `Html (PagesMsg msg)`, so we don't need to wrap its Msg type\n            , logoutForm\n                |> Pages.Form.renderHtml []\n                    Pages.Form.Serial\n                    (Form.options \"logout\"\n                        |> Form.withOnSubmit (\\_ -> NewItemSubmitted)\n                    )\n                    app\n            ]\n        }\n\n","type":"userMsg -> PagesMsg.PagesMsg userMsg"},{"name":"map","comment":" ","type":"(a -> b) -> PagesMsg.PagesMsg a -> PagesMsg.PagesMsg b"},{"name":"noOp","comment":" A Msg that is handled by the elm-pages framework and does nothing. Helpful for when you don't want to register a callback.\n\n    import Browser.Dom as Dom\n    import PagesMsg exposing (PagesMsg)\n    import Task\n\n    resetViewport : Cmd (PagesMsg msg)\n    resetViewport =\n        Dom.setViewport 0 0\n            |> Task.perform (\\() -> PagesMsg.noOp)\n\n","type":"PagesMsg.PagesMsg userMsg"}],"binops":[]},{"name":"Scaffold.Form","comment":" This module helps you with scaffolding a form in `elm-pages`, similar to how rails generators are used to scaffold out forms to\nget up and running quickly with the starting point for a form with different field types. See also [`Scaffold.Route`](Scaffold-Route).\n\nSee the `AddRoute` script in the starter template for an example. It's usually easiest to modify that script as a starting\npoint rather than using this API from scratch.\n\nUsing the `AddRoute` script from the default starter template, you can run a command like this:\n\n`npx elm-pages run AddRoute Profile.Username_.Edit first last bio:textarea dob:date` to generate a Route module `app/Route/Profile/Username_/Edit.elm`\nwith the wiring form a `Form`.\n\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages.com/docs/elm-pages-scripts#scaffolding-a-route-module).\n\n@docs Kind, provide, restArgsParser\n\n@docs Context\n\n@docs recordEncoder, fieldEncoder\n\n","unions":[{"name":"Kind","comment":" ","args":[],"cases":[["FieldInt",[]],["FieldText",[]],["FieldTextarea",[]],["FieldFloat",[]],["FieldTime",[]],["FieldDate",[]],["FieldCheckbox",[]]]}],"aliases":[{"name":"Context","comment":" ","args":[],"type":"{ errors : Elm.Expression, submitting : Elm.Expression, submitAttempted : Elm.Expression, data : Elm.Expression, expression : Elm.Expression }"}],"values":[{"name":"fieldEncoder","comment":" A lower-level, more granular version of `recordEncoder` - lets you generate a JSON Encoder `Expression` for an individual Field rather than a group of Fields.\n","type":"Elm.Expression -> String.String -> Scaffold.Form.Kind -> Elm.Expression"},{"name":"provide","comment":" ","type":"{ fields : List.List ( String.String, Scaffold.Form.Kind ), elmCssView : Basics.Bool, view : { formState : Scaffold.Form.Context, params : List.List { name : String.String, kind : Scaffold.Form.Kind, param : Elm.Expression } } -> Elm.Expression } -> Maybe.Maybe { formHandlers : Elm.Expression, form : Elm.Expression, declarations : List.List Elm.Declaration }"},{"name":"recordEncoder","comment":" Generate a JSON Encoder for the form fields. This can be helpful for sending the validated form data through a\nBackendTask.Custom or to an external API from your scaffolded Route Module code.\n","type":"Elm.Expression -> List.List ( String.String, Scaffold.Form.Kind ) -> Elm.Expression"},{"name":"restArgsParser","comment":" This parser handles the following field types (or `text` if none is provided):\n\n  - `text`\n  - `textarea`\n  - `checkbox`\n  - `time`\n  - `date`\n\nThe naming convention follows the same naming as the HTML form field elements or attributes that are used to represent them.\nIn addition to using the appropriate field type, this will also give you an Elm type with the corresponding base type (like `Date` for `date` or `Bool` for `checkbox`).\n\n","type":"Cli.Option.Option (List.List String.String) (List.List ( String.String, Scaffold.Form.Kind )) { position : Cli.Option.RestArgsOption }"}],"binops":[]},{"name":"Scaffold.Route","comment":" This module provides some functions for scaffolding code for a new Route Module. It uses [`elm-codegen`'s API](https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/) for generating code.\n\nTypically you'll want to use this via the `elm-pages run` CLI command. The default starter template includes a Script that uses these functions, which you can tweak to customize your scaffolding commands.\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages.com/docs/elm-pages-scripts#scaffolding-a-route-module).\n\nIt's typically easiest to modify the `AddRoute` script from the starter template and adjust it to your needs rather than writing one from scratch.\n\n\n## Initializing the Generator Builder\n\nThese functions mirror the `RouteBuilder` API that you use in your Route modules to define your route. The difference is that\ninstead of defining a route, this is defining a code generator for a Route module.\n\n@docs buildWithLocalState, buildWithSharedState, buildNoState, Builder\n\n@docs Type\n\n\n## Generating Server-Rendered Pages\n\n@docs serverRender\n\n\n## Generating pre-rendered pages\n\n@docs preRender, single\n\n\n## Including Additional elm-codegen Declarations\n\n@docs addDeclarations\n\n\n## CLI Options Parsing Helpers\n\n@docs moduleNameCliArg\n\n","unions":[{"name":"Builder","comment":" ","args":[],"cases":[]},{"name":"Type","comment":" ","args":[],"cases":[["Alias",["Elm.Annotation.Annotation"]],["Custom",["List.List Elm.Variant"]]]}],"aliases":[],"values":[{"name":"addDeclarations","comment":" The helpers in this module help you generate a Route module file with the core boilerplate abstracted away.\n\nYou can also define additional top-level declarations in the generated Route module using this helper.\n\n","type":"List.List Elm.Declaration -> Scaffold.Route.Builder -> Scaffold.Route.Builder"},{"name":"buildNoState","comment":" ","type":"{ view : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithLocalState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithSharedState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"moduleNameCliArg","comment":" A positional argument for elm-cli-options-parser that does a Regex validation to check that the module name is a valid Elm Route module name.\n","type":"Cli.Option.Option from String.String builderState -> Cli.Option.Option from (List.List String.String) builderState"},{"name":"preRender","comment":" Will scaffold using `RouteBuilder.preRender` if there are any dynamic segments (as in `Company.Team.Name_`),\nor using `RouteBuilder.single` if there are no dynamic segments (as in `Company.AboutUs`).\n\nWhen there are no dynamic segments, the `pages` field will be ignored as it is only relevant for Routes with dynamic segments.\n\nFor dynamic segments, the `routeParams` parameter in the `data` function will be an `Elm.Expression` with the `RouteParams` parameter in the `data` function.\nFor static segments, it will be a hardcoded empty record (`{}`).\n\n","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), pages : Elm.Expression, head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"serverRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression -> Elm.Expression ), action : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression -> Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"single","comment":" @depreacted. This is obsolete and will be removed in a future release. Use [`preRender`](#preRender) instead.\n\nIf you pass in only static route segments as the `moduleName` to `preRender` it will yield the same result as `single`.\n\n","type":"{ data : ( Scaffold.Route.Type, Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"}],"binops":[]},{"name":"Server.Request","comment":" Server-rendered Route modules and [server-rendered API Routes](ApiRoute#serverRender) give you access to a `Server.Request.Request` argument.\n\n@docs Request\n\nFor example, in a server-rendered route,\nyou could check a session cookie to decide whether to respond by rendering a page\nfor the logged-in user, or else respond with an HTTP redirect response (see the [`Server.Response` docs](Server-Response)).\n\nYou can access the incoming HTTP request's:\n\n  - [Headers](#headers)\n  - [Cookies](#cookies)\n  - [`method`](#method)\n  - [`rawUrl`](#rawUrl)\n  - [`requestTime`](#requestTime) (as a `Time.Posix`)\n\nThere are also some high-level helpers that take the low-level Request data and let you parse it into Elm types:\n\n  - [`jsonBody`](#jsonBody)\n  - [Form Helpers](#forms)\n  - [URL query parameters](#queryParam)\n  - [Content Type](#content-type)\n\nNote that this data is not available for pre-rendered pages or pre-rendered API Routes, only for server-rendered pages.\nThis is because when a page is pre-rendered, there _is_ no incoming HTTP request to respond to, it is rendered before a user\nrequests the page and then the pre-rendered page is served as a plain file (without running your Route Module).\n\nThat's why `RouteBuilder.preRender` does not have a `Server.Request.Request` argument.\n\n    import BackendTask exposing (BackendTask)\n    import RouteBuilder exposing (StatelessRoute)\n\n    type alias Data =\n        {}\n\n    data : RouteParams -> BackendTask Data\n    data routeParams =\n        BackendTask.succeed Data\n\n    route : StatelessRoute RouteParams Data ActionData\n    route =\n        RouteBuilder.preRender\n            { data = data\n            , head = head\n            , pages = pages\n            }\n            |> RouteBuilder.buildNoState { view = view }\n\nA server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page\nis loaded. That's why `data` has a `Server.Request.Request` argument in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,\n`RouteBuilder.serverRender` has `data : RouteParams -> Request -> BackendTask (Response Data)`. That means that you\ncan use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference\ncookie and render a light- or dark-themed page and render a different page.\n\n@docs requestTime\n\n\n## Request Headers\n\n@docs header, headers\n\n\n## Request Method\n\n@docs method, Method, methodToString\n\n\n## Request Body\n\n@docs body, jsonBody\n\n\n## Forms\n\n@docs formData, formDataWithServerValidation\n\n@docs rawFormData\n\n\n## URL\n\n@docs rawUrl\n\n@docs queryParam, queryParams\n\n\n## Content Type\n\n@docs matchesContentType\n\n\n## Using Cookies\n\n@docs cookie, cookies\n\n","unions":[{"name":"Method","comment":" An [Incoming HTTP Request Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).\n","args":[],"cases":[["Connect",[]],["Delete",[]],["Get",[]],["Head",[]],["Options",[]],["Patch",[]],["Post",[]],["Put",[]],["Trace",[]],["NonStandard",["String.String"]]]}],"aliases":[{"name":"Request","comment":" A value that lets you access data from the incoming HTTP request.\n","args":[],"type":"Internal.Request.Request"}],"values":[{"name":"body","comment":" The Request body, if present (or `Nothing` if there is no request body).\n","type":"Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookie","comment":" Get a cookie from the request. For a more high-level API, see [`Server.Session`](Server-Session).\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookies","comment":" Get all of the cookies from the incoming HTTP request. For a more high-level API, see [`Server.Session`](Server-Session).\n","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"formData","comment":" Takes a [`Form.Handler.Handler`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form-Handler) and\nparses the raw form data into a [`Form.Validated`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Validated) value.\n\nThis is the standard pattern for dealing with form data in `elm-pages`. You can share your code for your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Form)\ndefinitions between your client and server code, using this function to parse the raw form data into a `Form.Validated` value for the backend,\nand [`Pages.Form`](Pages-Form) to render the `Form` on the client.\n\nSince we are sharing the `Form` definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that\nthe validation errors that the user sees on the client are protected on our backend, and vice versa.\n\n    import BackendTask exposing (BackendTask)\n    import FatalError exposing (FatalError)\n    import Form\n    import Server.Request as Request exposing (Request)\n    import Server.Response as Response exposing (Response)\n\n    type Action\n        = Delete\n        | CreateOrUpdate Post\n\n    formHandlers : Form.Handler.Handler String Action\n    formHandlers =\n        deleteForm\n            |> Form.Handler.init (\\() -> Delete)\n            |> Form.Handler.with CreateOrUpdate createOrUpdateForm\n\n    deleteForm : Form.HtmlForm String () input msg\n\n    createOrUpdateForm : Form.HtmlForm String Post Post msg\n\n    action :\n        RouteParams\n        -> Request\n        -> BackendTask FatalError (Response ActionData ErrorPage)\n    action routeParams request =\n        case request |> Server.Request.formData formHandlers of\n            Nothing ->\n                BackendTask.fail (FatalError.fromString \"Missing form data\")\n\n            Just ( formResponse, parsedForm ) ->\n                case parsedForm of\n                    Form.Valid Delete ->\n                        deletePostBySlug routeParams.slug\n                            |> BackendTask.map\n                                (\\() -> Route.redirectTo Route.Index)\n\n                    Form.Valid (CreateOrUpdate post) ->\n                        let\n                            createPost : Bool\n                            createPost =\n                                okForm.slug == \"new\"\n                        in\n                        createOrUpdatePost post\n                            |> BackendTask.map\n                                (\\() ->\n                                    Route.redirectTo\n                                        (Route.Admin__Slug_ { slug = okForm.slug })\n                                )\n\n                    Form.Invalid _ invalidForm ->\n                        BackendTask.succeed\n                            (Server.Response.render\n                                { errors = formResponse }\n                            )\n\nYou can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests,\nsince a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by\nusing `POST` to ensure that you can't log users out by embedding an image with a logout URL in it.\n\nIf the request has HTTP method `GET`, the form data will come from the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Form.Handler.Handler error combined -> Server.Request.Request -> Maybe.Maybe ( Form.ServerResponse error, Form.Validated error combined )"},{"name":"formDataWithServerValidation","comment":" ","type":"Pages.Form.Handler error combined -> Server.Request.Request -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (Result.Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))"},{"name":"header","comment":" Get a header from the request. The header name is case-insensitive.\n\nHeader: Accept-Language: en-US,en;q=0.5\n\n    request |> Request.header \"Accept-Language\"\n    -- Just \"Accept-Language: en-US,en;q=0.5\"\n\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"headers","comment":" ","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"jsonBody","comment":" If the request has a body and its `Content-Type` matches JSON, then\ntry running a JSON decoder on the body of the request. Otherwise, return `Nothing`.\n\nExample:\n\n    Body: { \"name\": \"John\" }\n    Headers:\n    Content-Type: application/json\n    request |> jsonBody (Json.Decode.field \"name\" Json.Decode.string)\n    -- Just (Ok \"John\")\n\n    Body: { \"name\": \"John\" }\n    No Headers\n    jsonBody (Json.Decode.field \"name\" Json.Decode.string) request\n    -- Nothing\n\n    No Body\n    No Headers\n    jsonBody (Json.Decode.field \"name\" Json.Decode.string) request\n    -- Nothing\n\n","type":"Json.Decode.Decoder value -> Server.Request.Request -> Maybe.Maybe (Result.Result Json.Decode.Error value)"},{"name":"matchesContentType","comment":" True if the `content-type` header is present AND matches the given argument.\n\nExamples:\n\n    Content-Type: application/json; charset=utf-8\n    request |> matchesContentType \"application/json\"\n    -- True\n\n    Content-Type: application/json\n    request |> matchesContentType \"application/json\"\n    -- True\n\n    Content-Type: application/json\n    request |> matchesContentType \"application/xml\"\n    -- False\n\n","type":"String.String -> Server.Request.Request -> Basics.Bool"},{"name":"method","comment":" The [HTTP request method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) of the incoming request.\n\nNote that Route modules `data` is run for `GET` requests, and `action` is run for other request methods (including `POST`, `PUT`, `DELETE`).\nSo you don't need to check the `method` in your Route Module's `data` function, though you can choose to do so in its `action`.\n\n","type":"Server.Request.Request -> Server.Request.Method"},{"name":"methodToString","comment":" Gets the HTTP Method as an uppercase String.\n\nExamples:\n\n    Get\n        |> methodToString\n        -- \"GET\"\n\n","type":"Server.Request.Method -> String.String"},{"name":"queryParam","comment":" Get `Nothing` if the query param with the given name is missing, or `Just` the value if it is present.\n\nIf there are multiple query params with the same name, the first one is returned.\n\n    queryParam \"coupon\"\n\n    -- url: http://example.com?coupon=abc\n    -- parses into: Just \"abc\"\n\n    queryParam \"coupon\"\n\n    -- url: http://example.com?coupon=abc&coupon=xyz\n    -- parses into: Just \"abc\"\n\n    queryParam \"coupon\"\n\n    -- url: http://example.com\n    -- parses into: Nothing\n\nSee also [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level.\n\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"queryParams","comment":" Gives all query params from the URL.\n\n    queryParam \"coupon\"\n\n    -- url: http://example.com?coupon=abc\n    -- parses into: Dict.fromList [(\"coupon\", [\"abc\"])]\n\n    queryParam \"coupon\"\n\n    -- url: http://example.com?coupon=abc&coupon=xyz\n    -- parses into: Dict.fromList [(\"coupon\", [\"abc\", \"xyz\"])]\n\n","type":"Server.Request.Request -> Dict.Dict String.String (List.List String.String)"},{"name":"rawFormData","comment":" Get the raw key-value pairs from a form submission.\n\nIf the request has the HTTP method `GET`, it will return the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Server.Request.Request -> Maybe.Maybe (List.List ( String.String, String.String ))"},{"name":"rawUrl","comment":" The full URL of the incoming HTTP request, including the query params.\n\nNote that the fragment is not included because this is client-only (not sent to the server).\n\n    rawUrl request\n\n    -- url: http://example.com?coupon=abc\n    -- parses into: \"http://example.com?coupon=abc\"\n\n    rawUrl request\n\n    -- url: https://example.com?coupon=abc&coupon=xyz\n    -- parses into: \"https://example.com?coupon=abc&coupon=xyz\"\n\n","type":"Server.Request.Request -> String.String"},{"name":"requestTime","comment":" Get the `Time.Posix` when the incoming HTTP request was received.\n","type":"Server.Request.Request -> Time.Posix"}],"binops":[]},{"name":"Server.Response","comment":"\n\n\n## Responses\n\n@docs Response\n\n\n## Response's for Route Modules\n\nIn a server-rendered Route Module, you return a [`Response`](#Response). You'll typically want to return one of 3 types of Responses\nfrom your Route Modules:\n\n  - [`Server.Response.render`](#render) to render the current Route Module\n  - [`Server.Response.errorPage`](#errorPage) to render an ErrorPage\n  - [`Server.Response.temporaryRedirect`](#temporaryRedirect) to redirect to another page (the easiest way to build a redirect response is with `Route.redirectTo : Route -> Response data error`).\n\n```\n    import Server.Response as Response\n    import Route\n\n    data routeParams request =\n        case loggedInUser request of\n            Just user ->\n                findProjectById routeParams.id user\n                |> BackendTask.map\n                    (\\maybeProject ->\n                        case maybeProject of\n                            Just project ->\n                                Response.render project\n\n                            Nothing ->\n                                Response.errorPage ErrorPage.notFound\n                    )\n            Nothing ->\n                -- the generated module `Route` contains a high-level helper for returning a redirect `Response`\n                Route.redirectTo Route.Login\n```\n\n\n## Render Responses\n\n@docs render\n\n@docs map\n\n\n## Rendering Error Pages\n\n@docs errorPage, mapError\n\n\n## Redirects\n\n@docs temporaryRedirect, permanentRedirect\n\n\n## Response's for Server-Rendered ApiRoutes\n\nWhen defining your [server-rendered `ApiRoute`'s (`ApiRoute.serverRender`)](ApiRoute#serverRender) in your `app/Api.elm` module,\nyou can send a low-level server Response. You can set a String body,\na list of headers, the status code, etc. The Server Response helpers like `json` and `temporaryRedirect` are just helpers for\nbuilding up those low-level Server Responses.\n\nRender Responses are a little more special in the way they are connected to your elm-pages app. They allow you to render\nthe current Route Module. To do that, you'll need to pass along the `data` for your Route Module.\n\nYou can use `withHeader` and `withStatusCode` to customize either type of Response (Server Responses or Render Responses).\n\n\n## Response Body\n\n@docs json, plainText, emptyBody, body, bytesBody, base64Body\n\n\n## Amending Responses\n\n@docs withHeader, withHeaders, withStatusCode, withSetCookieHeader\n\n\n## Internals\n\n@docs toJson\n\n","unions":[],"aliases":[{"name":"Response","comment":" ","args":["data","error"],"type":"PageServerResponse.PageServerResponse data error"}],"values":[{"name":"base64Body","comment":" Build a `Response` with a String that should represent a base64 encoded value.\n\nYour adapter will need to handle `isBase64Encoded` to turn it into the appropriate response.\n\n    Response.base64Body \"SGVsbG8gV29ybGQ=\"\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"body","comment":" Same as [`plainText`](#plainText), but doesn't set a `Content-Type`.\n","type":"String.String -> Server.Response.Response data error"},{"name":"bytesBody","comment":" Build a `Response` with a `Bytes`.\n\nThe raw bytes will be sent through the port and base64-encoded on the JavaScript side.\nYour adapter will need to handle `isBase64Encoded` to turn it into the appropriate response.\n\n","type":"Bytes.Bytes -> Server.Response.Response data error"},{"name":"emptyBody","comment":" Build a `Response` with no HTTP response body.\n","type":"Server.Response.Response data error"},{"name":"errorPage","comment":" Instead of rendering the current Route Module, you can render an `ErrorPage` such as a 404 page or a 500 error page.\n\n[Read more about Error Pages](https://elm-pages.com/docs/error-pages) to learn about\ndefining and rendering your custom ErrorPage type.\n\n","type":"errorPage -> Server.Response.Response data errorPage"},{"name":"json","comment":" Build a JSON body from a `Json.Encode.Value`.\n\n    Json.Encode.object\n        [ ( \"message\", Json.Encode.string \"Hello\" ) ]\n        |> Response.json\n\nSets the `Content-Type` to `application/json`.\n\n","type":"Json.Encode.Value -> Server.Response.Response data error"},{"name":"map","comment":" Maps the `data` for a Render response. Usually not needed, but always good to have the option.\n","type":"(data -> mappedData) -> Server.Response.Response data error -> Server.Response.Response mappedData error"},{"name":"mapError","comment":" Maps the `error` for an ErrorPage response. Usually not needed, but always good to have the option.\n","type":"(errorPage -> mappedErrorPage) -> Server.Response.Response data errorPage -> Server.Response.Response data mappedErrorPage"},{"name":"permanentRedirect","comment":" Build a 308 permanent redirect response.\n\nPermanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,\nthen you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just\ntemporarily pointing them to a new page since they need to authenticate.\n\nPermanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.\n\nIf you need to specifically rely on a 301 permanent redirect (see <https://stackoverflow.com/a/42138726> on the difference between 301 and 308),\nuse `customResponse` instead.\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"plainText","comment":" Build a `Response` with a String body. Sets the `Content-Type` to `text/plain`.\n\n    Response.plainText \"Hello\"\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"render","comment":" Render the Route Module with the supplied data. Used for both the `data` and `action` functions in a server-rendered Route Module.\n\n    Response.render project\n\n","type":"data -> Server.Response.Response data error"},{"name":"temporaryRedirect","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"toJson","comment":" For internal use or more advanced use cases for meta frameworks.\n","type":"Server.Response.Response Basics.Never Basics.Never -> Json.Encode.Value"},{"name":"withHeader","comment":" Add a header to the response.\n\n    Response.plainText \"Hello!\"\n        -- allow CORS requests\n        |> Response.withHeader \"Access-Control-Allow-Origin\" \"*\"\n        |> Response.withHeader \"Access-Control-Allow-Methods\" \"GET, POST, OPTIONS\"\n\n","type":"String.String -> String.String -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withHeaders","comment":" Same as [`withHeader`](#withHeader), but allows you to add multiple headers at once.\n\n    Response.plainText \"Hello!\"\n        -- allow CORS requests\n        |> Response.withHeaders\n            [ ( \"Access-Control-Allow-Origin\", \"*\" )\n            , ( \"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\" )\n            ]\n\n","type":"List.List ( String.String, String.String ) -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withSetCookieHeader","comment":" Set a [`Server.SetCookie`](Server-SetCookie) value on the response.\n\nThe easiest way to manage cookies in your Routes is through the [`Server.Session`](Server-Session) API, but this\nprovides a more granular way to set cookies.\n\n","type":"Server.SetCookie.SetCookie -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withStatusCode","comment":" Set the [HTTP Response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) for the `Response`.\n\n    Response.plainText \"Not Authorized\"\n        |> Response.withStatusCode 401\n\n","type":"Basics.Int -> Server.Response.Response data Basics.Never -> Server.Response.Response data Basics.Never"}],"binops":[]},{"name":"Server.Session","comment":" You can manage server state with HTTP cookies using this Server.Session API. Server-rendered routes have a `Server.Request.Request`\nargument that lets you inspect the incoming HTTP request, and return a response using the `Server.Response.Response` type.\n\nThis API provides a higher-level abstraction for extracting data from the HTTP request, and setting data in the HTTP response.\nIt manages the session through key-value data stored in cookies, and lets you [`insert`](#insert), [`update`](#update), and [`remove`](#remove)\nvalues from the Session. It also provides an abstraction for flash session values through [`withFlash`](#withFlash).\n\n\n## Using Sessions\n\nUsing these functions, you can store and read session data in cookies to maintain state between requests.\n\n    import Server.Session as Session\n\n    secrets : BackendTask FatalError (List String)\n    secrets =\n        Env.expect \"SESSION_SECRET\"\n            |> BackendTask.allowFatal\n            |> BackendTask.map List.singleton\n\n    type alias Data =\n        { darkMode : Bool }\n\n    data : RouteParams -> Request -> BackendTask FatalError (Response Data ErrorPage)\n    data routeParams request =\n        request\n            |> Session.withSession\n                { name = \"mysession\"\n                , secrets = secrets\n                , options = Nothing\n                }\n                (\\session ->\n                    let\n                        darkMode : Bool\n                        darkMode =\n                            (session |> Session.get \"mode\" |> Maybe.withDefault \"light\")\n                                == \"dark\"\n                    in\n                    BackendTask.succeed\n                        ( session\n                        , Response.render\n                            { darkMode = darkMode\n                            }\n                        )\n                )\n\nThe elm-pages framework will manage signing these cookies using the `secrets : BackendTask FatalError (List String)` you pass in.\nThat means that the values you set in your session will be directly visible to anyone who has access to the cookie\n(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,\nthe cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been\nsigned with your secrets. Of course you need to provide secure secrets and treat your secrets with care.\n\n\n### Rotating Secrets\n\nThe first String in `secrets : BackendTask FatalError (List String)` will be used to sign sessions, while the remaining String's will\nstill be used to attempt to \"unsign\" the cookies. So if you have a single secret:\n\n    Session.withSession\n        { name = \"mysession\"\n        , secrets =\n            BackendTask.map List.singleton\n                (Env.expect \"SESSION_SECRET2022-09-01\")\n        , options = Nothing\n        }\n\nThen you add a second secret\n\n    Session.withSession\n        { name = \"mysession\"\n        , secrets =\n            BackendTask.map2\n                (\\newSecret oldSecret -> [ newSecret, oldSecret ])\n                (Env.expect \"SESSION_SECRET2022-12-01\")\n                (Env.expect \"SESSION_SECRET2022-09-01\")\n        , options = Nothing\n        }\n\nThe new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list\nwhenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get \"refreshed\" with the latest\nsigning secret when a new request comes in.\n\nHowever, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned\nbecause they are still in the rotation (and then subsequently \"refreshed\" and signed using the new secret).\n\nThis allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,\nit will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:\n\n    Session.withSession\n        { name = \"mysession\"\n        , secrets =\n            BackendTask.map List.singleton\n                (Env.expect \"SESSION_SECRET2022-12-01\")\n        , options = Nothing\n        }\n\nAnd then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid\n(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,\nso there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.\n\n@docs withSession, withSessionResult\n\n@docs NotLoadedReason\n\n\n## Creating and Updating Sessions\n\n@docs Session, empty, get, insert, remove, update, withFlash\n\n","unions":[{"name":"NotLoadedReason","comment":" [`withSessionResult`](#withSessionResult) will return a `Result` with this type if it can't load a session.\n","args":[],"cases":[["NoSessionCookie",[]],["InvalidSessionCookie",[]]]},{"name":"Session","comment":" Represents a Session with key-value Strings.\n\nUse with `withSession` to read in the `Session`, and encode any changes you make to the `Session` back through cookie storage\nvia the outgoing HTTP response.\n\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"empty","comment":" An empty `Session` with no key-value pairs.\n","type":"Server.Session.Session"},{"name":"get","comment":" Retrieve a String value from the session for the given key (or `Nothing` if the key is not present).\n\n    (session\n        |> Session.get \"mode\"\n        |> Maybe.withDefault \"light\"\n    )\n        == \"dark\"\n\n","type":"String.String -> Server.Session.Session -> Maybe.Maybe String.String"},{"name":"insert","comment":" Insert a value under the given key in the `Session`.\n\n    session\n        |> Session.insert \"mode\" \"dark\"\n\n","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"remove","comment":" Remove a key from the `Session`.\n","type":"String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"update","comment":" Update the `Session`, given a `Maybe String` of the current value for the given key, and returning a `Maybe String`.\n\nIf you return `Nothing`, the key-value pair will be removed from the `Session` (or left out if it didn't exist in the first place).\n\n    session\n        |> Session.update \"mode\"\n            (\\mode ->\n                case mode of\n                    Just \"dark\" ->\n                        Just \"light\"\n\n                    Just \"light\" ->\n                        Just \"dark\"\n\n                    Nothing ->\n                        Just \"dark\"\n            )\n\n","type":"String.String -> (Maybe.Maybe String.String -> Maybe.Maybe String.String) -> Server.Session.Session -> Server.Session.Session"},{"name":"withFlash","comment":" Flash session values are values that are only available for the next request.\n\n    session\n        |> Session.withFlash \"message\" \"Your payment was successful!\"\n\n","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"withSession","comment":" The main function for using sessions. If you need more fine-grained control over cases where a session can't be loaded, see\n[`withSessionResult`](#withSessionResult).\n","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"},{"name":"withSessionResult","comment":" Same as `withSession`, but gives you an `Err` with the reason why the Session couldn't be loaded instead of\nusing `Session.empty` as a default in the cases where there is an error loading the session.\n\nA session won't load if there is no session, or if it cannot be unsigned with your secrets. This could be because the cookie was tampered with\nor otherwise corrupted, or because the cookie was signed with a secret that is no longer in the rotation.\n\n","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Result.Result Server.Session.NotLoadedReason Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"}],"binops":[]},{"name":"Server.SetCookie","comment":" Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:\n\n  - [`Server.Session.withSession`](Server-Session#withSession)\n  - [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)\n\n[`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,\nwhereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the\nmost high-level tool that will fit your use case.\n\nYou can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:\n\n  - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>\n  - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>\n\n@docs SetCookie, setCookie\n\n\n## Building Options\n\nUsually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.\n\n    import Server.SetCookie as SetCookie\n\n    options : SetCookie.Options\n    options =\n        SetCookie.options\n            |> SetCookie.nonSecure\n            |> SetCookie.withMaxAge 123\n            |> SetCookie.makeVisibleToJavaScript\n            |> SetCookie.withoutPath\n            |> SetCookie.setCookie \"id\" \"a3fWa\"\n\n@docs Options, options\n\n@docs SameSite, withSameSite\n\n@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath\n\n\n## Internal\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).\n\nThe default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send\ncookies in cross-origin requests so it is a good default for most cases, but [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#strict)\nis even more restrictive).\n\nOverride the default option using [`withSameSite`](#withSameSite).\n\n","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"Options","comment":" The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.\n","args":[],"type":"{ expiration : Maybe.Maybe Time.Posix, visibleToJavaScript : Basics.Bool, maxAge : Maybe.Maybe Basics.Int, path : Maybe.Maybe String.String, domain : Maybe.Maybe String.String, secure : Basics.Bool, sameSite : Maybe.Maybe Server.SetCookie.SameSite }"},{"name":"SetCookie","comment":" ","args":[],"type":"{ name : String.String, value : String.String, options : Server.SetCookie.Options }"}],"values":[{"name":"makeVisibleToJavaScript","comment":" The default option in this API is for HttpOnly cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly>.\n\nCookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood\nthen there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that\ndynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.\n\nIn general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then\nit's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling\nin session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"nonSecure","comment":" Secure (only sent over https, or localhost on http) is the default. This overrides that and\nremoves the `Secure` attribute from the cookie.\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"options","comment":" Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.\n","type":"Server.SetCookie.Options"},{"name":"setCookie","comment":" Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can\npass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level\nuses you can stringify the value manually with [`toString`](#toString).\n","type":"String.String -> String.String -> Server.SetCookie.Options -> Server.SetCookie.SetCookie"},{"name":"toString","comment":" Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.\n\nThis is a low-level helper that's there in case you want it but most users will never need this.\n\n","type":"Server.SetCookie.SetCookie -> String.String"},{"name":"withDomain","comment":" Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withImmediateExpiration","comment":" Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,\nwhich effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withMaxAge","comment":" Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).\n","type":"Basics.Int -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withPath","comment":" Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).\n\nThe default value is `/`, which will match any sub-directories or the root directory. See also [\\`withoutPath](#withoutPath)\n\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withSameSite","comment":" The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes>.\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withoutPath","comment":"\n\n> If the server omits the Path attribute, the user agent will use the \"directory\" of the request-uri's path component as the default value.\n\nSource: <https://www.rfc-editor.org/rfc/rfc6265>. See <https://stackoverflow.com/a/43336097>.\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"}],"binops":[]},{"name":"Test.BackendTask","comment":" Write pure tests for your `BackendTask`s and `Script`s that you can run with `elm-test`.\nNo HTTP calls, no file I/O, or side effects of any kind. Just regular Elm tests.\n\nCommon scripting tasks like file operations, logging, and environment variables\nare emulated for you. You only need to tell the test how to simulate unpredictable\nevents in the outside world (like HTTP responses and shell commands), and what starting\nstate you want for your file system and environment variables. Everything\nelse will be emulated for you.\n\nYou can test a [`BackendTask`](BackendTask) directly using [`fromBackendTask`](#fromBackendTask)\nand [`fromBackendTaskWith`](#fromBackendTaskWith). For testing full `Script` values\nincluding CLI option parsing, use [`fromScript`](#fromScript) and [`fromScriptWith`](#fromScriptWith).\n\n\n## Predictable effects are emulated automatically\n\nNo simulation calls needed. Set the initial state with [`withFile`](#withFile)\nand assert on the results with [`ensureFile`](#ensureFile).\n\n    import BackendTask exposing (BackendTask)\n    import FatalError exposing (FatalError)\n    import Json.Decode as Decode\n    import Pages.Script as Script\n    import Test exposing (test)\n    import Test.BackendTask as BackendTaskTest\n\n    generateDotEnv : String -> BackendTask FatalError ()\n    generateDotEnv outputPath =\n        BackendTask.File.jsonFile configDecoder \"config.json\"\n            |> BackendTask.allowFatal\n            |> BackendTask.andThen\n                (\\config ->\n                    Script.writeFile\n                        { path = outputPath\n                        , body = toDotEnv config\n                        }\n                        |> BackendTask.allowFatal\n                )\n\n    test \"generates .env from config\" <|\n        \\() ->\n            generateDotEnv \".env\"\n                |> BackendTaskTest.fromBackendTaskWith\n                    (BackendTaskTest.init\n                        |> BackendTaskTest.withFile \"config.json\"\n                            \"\"\"{\"host\": \"localhost\", \"port\": 3000, \"debug\": true}\"\"\"\n                    )\n                |> BackendTaskTest.ensureFile \".env\"\n                    \"HOST=localhost\\nPORT=3000\\nDEBUG=true\"\n                |> BackendTaskTest.expectSuccess\n\n    configDecoder : Decode.Decoder { host : String, port_ : Int, debug : Bool }\n    configDecoder =\n        Decode.map3 (\\h p d -> { host = h, port_ = p, debug = d })\n            (Decode.field \"host\" Decode.string)\n            (Decode.field \"port\" Decode.int)\n            (Decode.field \"debug\" Decode.bool)\n\n    toDotEnv : { host : String, port_ : Int, debug : Bool } -> String\n    toDotEnv config =\n        [ \"HOST=\" ++ config.host\n        , \"PORT=\" ++ String.fromInt config.port_\n        , \"DEBUG=\" ++ (if config.debug then \"true\" else \"false\")\n        ]\n            |> String.join \"\\n\"\n\n\n## See Also\n\n  - [`Test.BackendTask.Time`](Test-BackendTask-Time) - configure virtual time zones\n  - [`Test.PagesProgram`](Test-PagesProgram) - end-to-end tests for elm-pages routes. Uses [`TestSetup`](#TestSetup) to seed initial state and reuses the simulators defined here for route-level HTTP and custom port responses.\n  - [`Test.Tui`](Test-Tui) - tests for `Tui.program` values. Uses [`TestSetup`](#TestSetup) to seed initial state and the simulators here to resolve `BackendTask` effects that a TUI performs via `Tui.Effect.perform`.\n\n\n## Automatic Virtual State Emulation\n\nThese effects are emulated automatically against virtual state:\n\n**File operations:**\n\n  - `Script.writeFile`, `Script.removeFile`, `Script.copyFile`, `Script.move` (assert with [`ensureFile`](#ensureFile))\n  - `BackendTask.File.rawFile`, `BackendTask.File.jsonFile`, `BackendTask.File.exists` ([`withFile`](#withFile) sets initial state)\n  - `BackendTask.Glob` (matches against files set with [`withFile`](#withFile))\n\n**Output:**\n\n  - `Script.log` (assert with [`ensureStdout`](#ensureStdout))\n\n**Environment:**\n\n  - `BackendTask.Env.get`, `BackendTask.Env.expect` ([`withEnv`](#withEnv) sets initial state)\n  - `BackendTask.Time.now` ([`withTime`](#withTime) sets initial state)\n  - `BackendTask.Time.zone`, `BackendTask.Time.zoneFor`, `BackendTask.Time.zoneByName`, `BackendTask.Time.zoneByNameFor` ([`Test.BackendTask.Time.withTimeZone`](Test-BackendTask-Time#withTimeZone) / [`Test.BackendTask.Time.withTimeZoneByName`](Test-BackendTask-Time#withTimeZoneByName) sets initial state)\n  - `BackendTask.Random.generate` ([`withRandomSeed`](#withRandomSeed) sets initial state)\n  - `Script.which` ([`withWhich`](#withWhich) sets initial state)\n\n**Database:**\n\n  - `Pages.Db.get`, `Pages.Db.update`, `Pages.Db.transaction` ([`withDb`](#withDb) sets initial state, assert with [`expectDb`](#expectDb))\n\n**CLI parsing:**\n\n  - `Script.withCliOptions`, `Script.withoutCliOptions` (parsed from args passed to [`fromScript`](#fromScript))\n\n**Streams:**\n\n  - `Stream.fromString`, `Stream.stdin`, `Stream.stdout`, `Stream.stderr`\n  - `Stream.fileRead`, `Stream.fileWrite`, `Stream.gzip`, `Stream.unzip`\n\nEverything else represents outside data and effects (HTTP requests, shell commands, [`BackendTask.Custom`](BackendTask-Custom) calls)\nwhich you must simulate using the functions below.\nIf your test case encounters one of these which is not simulated, it will fail with a clear message with instructions for how to\nsimulate it.\n\n\n## Building\n\n@docs fromBackendTask, fromBackendTaskWith\n\n@docs fromScript, fromScriptWith\n\n\n## Test Setup\n\nSeed initial state before the test starts running.\n\n@docs TestSetup, init, withFile, withBinaryFile, withStdin, withEnv, withTime, withRequestTime, withRequestHeader, withRandomSeed, withWhich\n\n## Companion Module Helpers\n\nLow-level setup helpers used by [`Test.BackendTask.Time`](Test-BackendTask-Time).\n\n@docs withTimeZoneConfig, withTimeZoneByNameConfig\n\n@docs withDb, withDbSetTo\n\n\n## Simulating HTTP\n\n@docs simulateHttpGet, simulateHttpPost, simulateHttp, simulateHttpError, simulateHttpStream\n\n@docs HttpError\n\n\n## Simulating Commands\n\n@docs simulateCommand\n\n\n## Simulating Custom Ports\n\n@docs simulateCustom, simulateCustomStream\n\n\n## Simulating Interactive Input\n\n@docs simulateQuestion, simulateReadKey\n\n\n## Assertions\n\nCheck conditions mid-pipeline without ending the test. These return the same\n`BackendTaskTest` so you can keep chaining.\n\n@docs Output, ensureStdout, ensureStderr, ensureOutputWith\n\n@docs ensureFile, ensureFileExists, ensureNoFile, ensureFileWritten\n\n@docs ensureHttpGet, ensureHttpPost\n\n@docs ensureCustom\n\n@docs ensureCommand\n\n\n## Virtual Effects\n\nDeclare virtual effects for opaque external operations. Today these effects update the\nvirtual filesystem. When a call is resolved\nvia [`simulateCustom`](#simulateCustom) or\n[`simulateCommand`](#simulateCommand), the registered handler's\neffects are applied to the virtual filesystem automatically.\n\n@docs SimulatedEffect, withVirtualEffects, writeFileEffect, removeFileEffect\n\n\n## Terminal Assertions\n\nEvery test must end with exactly one of these to produce an `Expectation`.\n\n@docs expectSuccess, expectSuccessWith, expectFailure, expectFailureWith, expectTestError\n\n@docs expectDb\n\n","unions":[{"name":"HttpError","comment":" The type of HTTP error to simulate with [`simulateHttpError`](#simulateHttpError).\n\n    BackendTaskTest.simulateHttpError\n        \"GET\"\n        \"https://api.example.com/data\"\n        BackendTaskTest.NetworkError\n\n","args":[],"cases":[["NetworkError",[]],["Timeout",[]]]},{"name":"Output","comment":" Represents a single output message on either stdout or stderr, preserving\nthe interleaved ordering. Used with [`ensureOutputWith`](#ensureOutputWith).\n","args":[],"cases":[["Stdout",["String.String"]],["Stderr",["String.String"]]]},{"name":"SimulatedEffect","comment":" An effect on the virtual filesystem declared by an opaque external operation\n(shell command or `BackendTask.Custom.run` call) via [`withVirtualEffects`](#withVirtualEffects).\nCreate values with [`writeFileEffect`](#writeFileEffect) and [`removeFileEffect`](#removeFileEffect).\n","args":[],"cases":[]}],"aliases":[{"name":"TestSetup","comment":" The virtual environment used to start a backend task test.\n\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withFile \"config.json\" \"\"\"{\"key\":\"value\"}\"\"\"\n\n","args":[],"type":"Test.BackendTask.Internal.TestSetup"}],"values":[{"name":"ensureCommand","comment":" Assert that a command with the given name is currently pending, and run an\nassertion on its arguments. Does not resolve the request. Useful for verifying\ncommands are called with expected arguments before simulating them.\n\n    import Test.BackendTask as BackendTaskTest\n\n    Script.exec \"elm\" [ \"make\", \"--docs=docs.json\" ]\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureCommand \"elm\"\n            (\\args -> Expect.equal [ \"make\", \"--docs=docs.json\" ] args)\n        |> BackendTaskTest.simulateCommand \"elm\" \"\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> (List.List String.String -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureCustom","comment":" Assert that a `BackendTask.Custom.run` call with the given port name is currently\npending, and run an assertion on the input arguments. Does not resolve the request.\n\n    import Expect\n    import Json.Decode as Decode\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureCustom \"hashPassword\"\n            (\\args ->\n                Decode.decodeValue Decode.string args\n                    |> Expect.equal (Ok \"secret123\")\n            )\n        |> ...\n\n","type":"String.String -> (Json.Encode.Value -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureFile","comment":" Assert that a file exists in the virtual filesystem with the given content.\n\n    Script.writeFile { path = \"output.txt\", body = \"hello\" }\n        |> BackendTask.allowFatal\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureFile \"output.txt\" \"hello\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureFileExists","comment":" Assert that a file exists in the virtual filesystem (without checking its content).\n","type":"String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureFileWritten","comment":" Assert that a file write occurred with the given path and body.\n\n    Script.writeFile { path = \"out.json\", body = \"\"\"{\"key\":\"value\"}\"\"\" }\n        |> BackendTask.allowFatal\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureFileWritten { path = \"out.json\", body = \"\"\"{\"key\":\"value\"}\"\"\" }\n        |> BackendTaskTest.expectSuccess\n\n","type":"{ path : String.String, body : String.String } -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureHttpGet","comment":" Assert that a GET request to the given URL is currently pending, without resolving it.\nUseful for verifying that requests are dispatched in parallel.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureHttpGet \"https://api.example.com/a\"\n        |> BackendTaskTest.ensureHttpGet \"https://api.example.com/b\"\n        |> ...\n\n","type":"String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureHttpPost","comment":" Assert that a POST request to the given URL is currently pending, and run an\nassertion on the request body. Does not resolve the request.\n\n    import Expect\n    import Json.Decode as Decode\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureHttpPost \"https://api.example.com/items\"\n            (\\body ->\n                Decode.decodeValue (Decode.field \"name\" Decode.string) body\n                    |> Expect.equal (Ok \"test\")\n            )\n        |> ...\n\n","type":"String.String -> (Json.Encode.Value -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureNoFile","comment":" Assert that a file does not exist in the virtual filesystem.\n","type":"String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureOutputWith","comment":" Assert on interleaved stdout/stderr output, preserving ordering.\nDrains on success, preserves on failure.\n\n    import Test.BackendTask as BackendTaskTest exposing (Output(..))\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureOutputWith\n            (\\outputs ->\n                Expect.equal\n                    [ Stdout \"step 1\", Stderr \"warning!\", Stdout \"step 2\" ]\n                    outputs\n            )\n        |> BackendTaskTest.expectSuccess\n\n","type":"(List.List Test.BackendTask.Output -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureStderr","comment":" Assert that exactly these stderr messages were produced since the last drain.\nFails if any stdout output is present. Use [`ensureOutputWith`](#ensureOutputWith)\nto check both stdout and stderr together.\n\n    Stream.fromString \"warning!\"\n        |> Stream.pipe Stream.stderr\n        |> Stream.run\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureStderr [ \"warning!\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"List.List String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"ensureStdout","comment":" Assert that exactly these stdout messages were produced since the last drain.\nFails if any stderr output is present. Use [`ensureOutputWith`](#ensureOutputWith)\nto check both stdout and stderr together.\n\n    Script.log \"Hello!\"\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.ensureStdout [ \"Hello!\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"List.List String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"expectDb","comment":" Assert on the virtual DB state after the script completes. This is a terminal\nassertion that also checks the script completed successfully.\n\n    import Expect\n    import Pages.Db\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTaskWith\n            (BackendTaskTest.init\n                |> BackendTaskTest.withDb Pages.Db.testConfig\n            )\n        |> BackendTaskTest.expectDb Pages.Db.testConfig\n            (\\db -> Expect.equal 1 db.counter)\n\n","type":"{ a | decode : Bytes.Bytes -> Maybe.Maybe db } -> (db -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest b -> Expect.Expectation"},{"name":"expectFailure","comment":" Assert that the `BackendTask` completed with a `FatalError`.\n","type":"Test.BackendTask.Internal.BackendTaskTest a -> Expect.Expectation"},{"name":"expectFailureWith","comment":" Like [`expectFailure`](#expectFailure), but also runs an assertion on the error.\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.expectFailureWith\n            (\\error -> error.title |> Expect.equal \"Http Error\")\n\n","type":"({ title : String.String, body : String.String } -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Expect.Expectation"},{"name":"expectSuccess","comment":" Assert that the `BackendTask` completed successfully.\n\n    BackendTask.succeed ()\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.expectSuccess\n\nIf there are still pending requests, the test fails with a message listing them.\n\n","type":"Test.BackendTask.Internal.BackendTaskTest a -> Expect.Expectation"},{"name":"expectSuccessWith","comment":" Like [`expectSuccess`](#expectSuccess), but also runs an assertion on the result value.\n\n    BackendTask.succeed 42\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.expectSuccessWith (Expect.equal 42)\n\n","type":"(a -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Expect.Expectation"},{"name":"expectTestError","comment":" Assert that the test framework itself produced an error (e.g., a simulation that\ndidn't match any pending request, or a missing `withTime` configuration).\n\n    BackendTask.Time.now\n        |> BackendTask.andThen (\\_ -> BackendTask.succeed ())\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.expectTestError\n            (Expect.equal \"BackendTask.Time.now requires a virtual time.\\\\n\\\\n...\")\n\n","type":"(String.String -> Expect.Expectation) -> Test.BackendTask.Internal.BackendTaskTest a -> Expect.Expectation"},{"name":"fromBackendTask","comment":" Start a test from a `BackendTask FatalError a`. Predictable effects like `Script.log`\nand `Script.writeFile` are resolved automatically. You only need to simulate external\neffects like HTTP requests and `BackendTask.Custom.run` calls.\n\n    import BackendTask\n    import Pages.Script as Script\n    import Test.BackendTask as BackendTaskTest\n\n    Script.log \"Hello!\"\n        |> BackendTask.allowFatal\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.expectSuccess\n\n","type":"BackendTask.BackendTask FatalError.FatalError a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"fromBackendTaskWith","comment":" Start a test with a configured [`TestSetup`](#init).\nUse this when you need to seed initial files, environment variables, or other state.\n\n    import BackendTask.File\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTask.File.rawFile \"config.json\"\n        |> BackendTask.allowFatal\n        |> BackendTask.andThen (\\content -> Script.log content)\n        |> BackendTaskTest.fromBackendTaskWith\n            (BackendTaskTest.init\n                |> BackendTaskTest.withFile \"config.json\" \"\"\"{\"key\":\"value\"}\"\"\"\n            )\n        |> BackendTaskTest.ensureStdout [ \"\"\"{\"key\":\"value\"}\"\"\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"Test.BackendTask.TestSetup -> BackendTask.BackendTask FatalError.FatalError a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"fromScript","comment":" Start a test from a [`Script`](Pages-Script#Script) with simulated CLI arguments.\nThis tests the full script including CLI option parsing.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myScript\n        |> BackendTaskTest.fromScript [ \"--name\", \"Dillon\" ]\n        |> BackendTaskTest.ensureStdout [ \"Hello, Dillon!\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"List.List String.String -> Pages.Script.Script -> Test.BackendTask.Internal.BackendTaskTest ()"},{"name":"fromScriptWith","comment":" Like [`fromScript`](#fromScript) but with a configured [`TestSetup`](#init).\n\n    import Test.BackendTask as BackendTaskTest\n\n    myScript\n        |> BackendTaskTest.fromScriptWith\n            (BackendTaskTest.init\n                |> BackendTaskTest.withFile \"config.json\" \"{}\"\n            )\n            []\n        |> BackendTaskTest.expectSuccess\n\n","type":"Test.BackendTask.TestSetup -> List.List String.String -> Pages.Script.Script -> Test.BackendTask.Internal.BackendTaskTest ()"},{"name":"init","comment":" An empty test setup with no seeded state.\n","type":"Test.BackendTask.TestSetup"},{"name":"removeFileEffect","comment":" Create a simulated file removal effect.\n\n    BackendTaskTest.removeFileEffect \"temp.txt\"\n\n","type":"String.String -> Test.BackendTask.SimulatedEffect"},{"name":"simulateCommand","comment":" Simulate a pending `Stream.command` / `Script.command` / `Script.exec` call.\nThe framework handles simulatable parts around the command. You provide the\ncommand name and its output.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateCommand \"grep\" \"matched line\\n\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateCustom","comment":" Simulate a pending `BackendTask.Custom.run` call resolving with the given JSON value.\nThe port name must exactly match the first argument passed to `BackendTask.Custom.run`.\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateCustom \"hashPassword\" (Encode.string \"hashed_secret123\")\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> Json.Encode.Value -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateCustomStream","comment":" Simulate a pending stream pipeline containing a custom stream part\n(`Stream.customRead`, `Stream.customWrite`, or `Stream.customDuplex`).\nThe framework handles simulatable parts around the custom part. You only\nprovide its output.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateCustomStream \"myTransform\" \"transformed output\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateHttp","comment":" Simulate any HTTP request with full control over method, status code, headers, and body.\n\n    import Test.BackendTask as BackendTaskTest\n    import Json.Encode as Encode\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateHttp\n            { method = \"PUT\", url = \"https://api.example.com/item/1\" }\n            { statusCode = 204\n            , statusText = \"No Content\"\n            , headers = []\n            , body = Encode.null\n            }\n        |> BackendTaskTest.expectSuccess\n\n","type":"{ method : String.String, url : String.String } -> { statusCode : Basics.Int, statusText : String.String, headers : List.List ( String.String, String.String ), body : Json.Encode.Value } -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateHttpError","comment":" Simulate a pending HTTP request failing with an [`HttpError`](#HttpError).\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateHttpError \"GET\" \"https://api.example.com/data\" BackendTaskTest.NetworkError\n        |> BackendTaskTest.expectFailure\n\n","type":"String.String -> String.String -> Test.BackendTask.HttpError -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateHttpGet","comment":" Simulate a pending HTTP GET request resolving with the given JSON response body.\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateHttpGet\n            \"https://api.example.com/data\"\n            (Encode.object [ ( \"key\", Encode.string \"value\" ) ])\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> Json.Encode.Value -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateHttpPost","comment":" Simulate a pending HTTP POST request resolving with the given JSON response body.\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateHttpPost\n            \"https://api.example.com/items\"\n            (Encode.object [ ( \"id\", Encode.int 42 ) ])\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> Json.Encode.Value -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateHttpStream","comment":" Simulate a pending stream pipeline containing an HTTP stream part\n(`Stream.http` or `Stream.httpWithInput`). The framework handles simulatable\nparts around the HTTP request. You only provide the response body.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateHttpStream \"https://api.example.com\" \"response body\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateQuestion","comment":" Simulate a pending `Script.question` call. The prompt must match the prompt\ntext passed to `Script.question`.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateQuestion \"What is your name? \" \"Dillon\"\n        |> BackendTaskTest.ensureStdout [ \"Hello, Dillon!\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"simulateReadKey","comment":" Simulate a pending `Script.readKey` call resolving with the given key.\n\n    import Test.BackendTask as BackendTaskTest\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.simulateReadKey \"y\"\n        |> BackendTaskTest.ensureStdout [ \"confirmed\" ]\n        |> BackendTaskTest.expectSuccess\n\n","type":"String.String -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"withBinaryFile","comment":" Seed a binary file into the virtual filesystem.\n\n    import Bytes.Encode\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withBinaryFile \"data.bin\"\n            (Bytes.Encode.encode\n            (Bytes.Encode.unsignedInt8 42)\n            )\n\n","type":"String.String -> Bytes.Bytes -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withDb","comment":" Seed the virtual DB with the default seed value from the generated `testConfig`.\n\nThe [elm-pages Script DB](https://elm-pages.com/docs/elm-pages-scripts-db/) is a\npure Elm type that is serialized to disk. Since it's just a plain Elm value, the\ntest framework can realistically simulate it. Reads, writes, and updates all\nwork against an in-memory copy. The only difference from production is that\ninstead of reading the initial value from a binary file of the serialized Elm\ntype on disk, you provide it directly with this function or [`withDbSetTo`](#withDbSetTo).\n\n    import Pages.Db\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withDb Pages.Db.testConfig\n\n","type":"{ a | schemaVersion : Basics.Int, schemaHash : String.String, encode : db -> Bytes.Bytes, seed : db } -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withDbSetTo","comment":" Seed the virtual DB with a specific initial value.\n\n    import Pages.Db\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withDbSetTo { counter = 5 } Pages.Db.testConfig\n\n","type":"db -> { a | schemaVersion : Basics.Int, schemaHash : String.String, encode : db -> Bytes.Bytes } -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withEnv","comment":" Seed an environment variable for `BackendTask.Env.get` and `BackendTask.Env.expect`.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withEnv \"API_KEY\" \"secret123\"\n\n","type":"String.String -> String.String -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withFile","comment":" Seed a file into the virtual filesystem.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withFile \"config.json\"\n            \"\"\"{\"key\":\"value\"}\"\"\"\n\n","type":"String.String -> String.String -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withRandomSeed","comment":" Set a fixed random seed for `BackendTask.Random.int32` and `BackendTask.Random.generate`.\nWithout this, any use of `BackendTask.Random` will produce a test error with a helpful message.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withRandomSeed 42\n\n","type":"Basics.Int -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withRequestHeader","comment":" Seed a request header for server-rendered route requests in\n[`Test.PagesProgram.start`](Test-PagesProgram#start).\n\nHeader names are normalized to lowercase.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withRequestHeader \"accept-language\" \"en-US\"\n\n","type":"String.String -> String.String -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withRequestTime","comment":" Set a fixed request time for server-rendered route requests in\n[`Test.PagesProgram.start`](Test-PagesProgram#start).\n\n    import Time\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withRequestTime (Time.millisToPosix 1709827200000)\n\n","type":"Time.Posix -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withStdin","comment":" Seed stdin content for stream pipelines that read from `Stream.stdin`.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withStdin \"hello from stdin\"\n\n","type":"String.String -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withTime","comment":" Set a fixed virtual time for `BackendTask.Time.now`. Without this, any use of\n`BackendTask.Time.now` will produce a test error with a helpful message.\n\n    import Time\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withTime (Time.millisToPosix 0)\n\n","type":"Time.Posix -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withTimeZoneByNameConfig","comment":" Low-level helper used by [`Test.BackendTask.Time.withTimeZoneByName`](Test-BackendTask-Time#withTimeZoneByName).\n\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withTimeZoneByNameConfig \"America/Chicago\"\n            { defaultOffset = -360, eras = [] }\n\n","type":"String.String -> { defaultOffset : Basics.Int, eras : List.List { start : Basics.Int, offset : Basics.Int } } -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withTimeZoneConfig","comment":" Low-level helper used by [`Test.BackendTask.Time.withTimeZone`](Test-BackendTask-Time#withTimeZone).\n\n    import Test.BackendTask as BackendTaskTest\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withTimeZoneConfig\n            { defaultOffset = 0, eras = [] }\n\n","type":"{ defaultOffset : Basics.Int, eras : List.List { start : Basics.Int, offset : Basics.Int } } -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withVirtualEffects","comment":" Declare virtual filesystem effects for opaque external operations. The handler\nreceives the operation name (port name or command name) and request body, and returns\na list of [`SimulatedEffect`](#SimulatedEffect)s applied to the virtual filesystem\nwhen the operation is simulated. For custom ports the body is the JSON input passed to\n`BackendTask.Custom.run`. For commands the body is the args as a JSON list of strings.\n\nThis fires for both [`simulateCustom`](#simulateCustom) and\n[`simulateCommand`](#simulateCommand).\n\n    myTask\n        |> BackendTaskTest.fromBackendTask\n        |> BackendTaskTest.withVirtualEffects\n            (\\name _ ->\n                case name of\n                    \"mkdir\" ->\n                        [ BackendTaskTest.writeFileEffect \"new-dir/.gitkeep\" \"\" ]\n\n                    \"generateReport\" ->\n                        [ BackendTaskTest.writeFileEffect \"report.pdf\" \"content\" ]\n\n                    _ ->\n                        []\n            )\n        |> BackendTaskTest.simulateCommand \"mkdir\" \"\"\n        |> BackendTaskTest.ensureFileExists \"new-dir/.gitkeep\"\n        |> BackendTaskTest.expectSuccess\n\n","type":"(String.String -> Json.Encode.Value -> List.List Test.BackendTask.SimulatedEffect) -> Test.BackendTask.Internal.BackendTaskTest a -> Test.BackendTask.Internal.BackendTaskTest a"},{"name":"withWhich","comment":" Register a command as available for `Script.which` and `Script.expectWhich`.\nCommands not registered will return `Nothing` from `Script.which`.\n\n    BackendTaskTest.init\n        |> BackendTaskTest.withWhich \"node\" \"/usr/bin/node\"\n\n","type":"String.String -> String.String -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"writeFileEffect","comment":" Create a simulated file write effect.\n\n    BackendTaskTest.writeFileEffect \"output.txt\" \"file content\"\n\n","type":"String.String -> String.String -> Test.BackendTask.SimulatedEffect"}],"binops":[]},{"name":"Test.BackendTask.Time","comment":" Configure virtual time zones for BackendTask tests.\n\nTo set the virtual time itself, see [`Test.BackendTask.withTime`](Test-BackendTask#withTime).\n\n\n## TimeZone\n\n@docs TimeZone\n\n\n## Constructors\n\n@docs utc, fixedOffsetZone, customTimeZone\n\n\n## Test Setup\n\n@docs withTimeZone, withTimeZoneByName\n\n","unions":[{"name":"TimeZone","comment":" Represents a time zone for use in tests with [`withTimeZone`](#withTimeZone) and\n[`withTimeZoneByName`](#withTimeZoneByName). Create values with [`utc`](#utc),\n[`fixedOffsetZone`](#fixedOffsetZone), or [`customTimeZone`](#customTimeZone).\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"customTimeZone","comment":" A time zone with a default offset and a list of era transitions. Each era\nspecifies a start time (minutes since epoch) and its UTC offset in minutes.\nThese values match the format used by [`Time.customZone`](https://package.elm-lang.org/packages/elm/time/latest/Time#customZone).\n\n    import Test.BackendTask.Time as BackendTaskTime\n\n    -- Mar 10, 2024 07:00 UTC = 1710054000000 ms / 60000 = 28500900 minutes\n    BackendTaskTime.customTimeZone -300\n        [ { start = 28500900, offset = -240 } ]\n\n","type":"Basics.Int -> List.List { start : Basics.Int, offset : Basics.Int } -> Test.BackendTask.Time.TimeZone"},{"name":"fixedOffsetZone","comment":" A time zone with a fixed offset in minutes from UTC. Negative values are\nwest of UTC (e.g., -300 for US Eastern Standard Time).\n\n    import Test.BackendTask.Time as BackendTaskTime\n\n    BackendTaskTime.fixedOffsetZone -300\n\n","type":"Basics.Int -> Test.BackendTask.Time.TimeZone"},{"name":"utc","comment":" UTC time zone (offset 0).\n\n    import Test.BackendTask as BackendTaskTest\n    import Test.BackendTask.Time as BackendTaskTime\n\n    BackendTaskTest.init\n        |> BackendTaskTime.withTimeZone BackendTaskTime.utc\n\n","type":"Test.BackendTask.Time.TimeZone"},{"name":"withTimeZone","comment":" Set the default virtual time zone for `BackendTask.Time.zone` and\n`BackendTask.Time.zoneFor`.\n\n    import Test.BackendTask as BackendTaskTest\n    import Test.BackendTask.Time as BackendTaskTime\n\n    BackendTaskTest.init\n        |> BackendTaskTime.withTimeZone BackendTaskTime.utc\n\n","type":"Test.BackendTask.Time.TimeZone -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"},{"name":"withTimeZoneByName","comment":" Register a named time zone for `BackendTask.Time.zoneByName` and\n`BackendTask.Time.zoneByNameFor`.\n\n    import Test.BackendTask as BackendTaskTest\n    import Test.BackendTask.Time as BackendTaskTime\n\n    BackendTaskTest.init\n        |> BackendTaskTime.withTimeZoneByName \"America/Chicago\"\n            (BackendTaskTime.fixedOffsetZone -360)\n\n","type":"String.String -> Test.BackendTask.Time.TimeZone -> Test.BackendTask.TestSetup -> Test.BackendTask.TestSetup"}],"binops":[]},{"name":"Test.PagesProgram","comment":" Write pure tests for your elm-pages Route Modules. These tests will not actually perform\nany HTTP requests or outside interactions (from BackendTask, or from a Route's `init`/`update` function). For any\n`BackendTask` that can't be simulated (i.e. outside world interactions like HTTP requests),\nyou pass in the fake data to simulate the real world result using the [`Test.BackendTask`](Test-BackendTask) API.\n\nBecause the test framework simulates the core pieces of the browser and elm-pages runtime, including\ncookies, URL state, and navigation, your tests realistically check\nend-to-end behavior deterministically and efficiently:\n\n  - [`BackendTask`](BackendTask) data loading and actions, with simulated responses for [`BackendTask.Http`](BackendTask-Http) and [`BackendTask.Custom`](BackendTask-Custom)\n  - [`Pages.Form`](Pages-Form) submissions, including [concurrent submissions](Pages-Form#withConcurrent)\n  - Optimistic UI via [`Pages.ConcurrentSubmission`](Pages-ConcurrentSubmission) - assert on the view while fetchers are still in-flight\n  - [`Server.Session`](Server-Session) and [`Server.SetCookie`](Server-SetCookie) - cookies persist across navigations and form submissions within a test\n  - [`Server.Response`](Server-Response) redirects are followed automatically, with cookie propagation\n  - Server-rendered [Error Page](https://elm-pages.com/docs/error-pages) responses are rendered and can be tested like any other page\n  - Client-side TEA interactions via [`SimulatedEffect`](Test-PagesProgram-SimulatedEffect) - `Effect.sendMsg` dispatches through the update cycle\n\n\n## Quick start\n\nSay you have a route that fetches GitHub stars via a BackendTask:\n\n    -- app/Route/Index.elm\n    type alias Data =\n        { stars : Int }\n\n    data : BackendTask FatalError Data\n    data =\n        BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Decode.field \"stargazers_count\" Decode.int)\n            |> BackendTask.allowFatal\n            |> BackendTask.map Data\n\n    view app _ _ =\n        { title = \"Home\"\n        , body = [ Html.text (\"Stars: \" ++ String.fromInt app.data.stars) ]\n        }\n\nLike a regular `elm-test` suite, you build named tests with\n[`test`](#test) and group them with [`describe`](#describe). Each test\ntakes a starting `TestApp.ProgramTest` and a `List` of [`Step`](#Step)\nvalues; the framework runs the steps and finalizes the test for you.\nThe exposed value is a `Test.PagesProgram.Test`, and `elm-pages`\ndiscovers it by name (typically `suite`).\n\n    module MyTests exposing (suite)\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n    import Test.Html.Selector as Selector\n    import Test.PagesProgram as PagesProgram\n    import TestApp\n\n    suite : PagesProgram.Test\n    suite =\n        PagesProgram.describe \"GitHub stars\"\n            [ PagesProgram.test \"renders the star count from the API\"\n                (TestApp.start \"/\" BackendTaskTest.init)\n                [ PagesProgram.simulateHttpGet\n                    \"https://api.github.com/repos/dillonkearns/elm-pages\"\n                    (Encode.object [ ( \"stargazers_count\", Encode.int 9999 ) ])\n                , PagesProgram.ensureViewHas [ Selector.text \"Stars: 9999\" ]\n                ]\n            ]\n\nThe `TestApp` module is generated by `elm-pages`, you just import it\nand then use it to begin your test program pipelines. Tests can be\nrun via the `elm-pages test` CLI or viewed in the\n[visual test runner](#visual-test-runner) through the dev server.\n\nYou can run tests headlessly (via elm-test) or view them\nin the visual test runner in the browser.\n\n```sh\nnpx elm-pages test\n\n## Or view it in your dev server\nnpx elm-pages dev\nopen http://localhost:1234/_tests\n```\n\n\n## Visual Test Runner\n\nThe visual test runner lets you time travel through each step\nof a test in the browser. While the test run itself is pure\n(no HTTP requests to the outside world, etc.), the view is rendered\nwith your full Vite configuration so any styles are applied as they\nwould be in your dev server or production build.\n\nThe visual test runner also includes inspectors for the network requests,\nform fetcher state, model, and other tools that let you inspect what's\ngoing on with your app during any given step.\n\n![Visual Test Runner](https://raw.githubusercontent.com/dillonkearns/elm-pages/visual-test-runner/docs/visual-test-runner.png)\n\n@docs ProgramTest, Step\n\n\n## Building test suites\n\nBuild a named test tree just like `elm-test`. [`test`](#test) names a\nsingle test from a starting `ProgramTest` and a list of steps;\n[`describe`](#describe) groups related tests under a shared heading.\nUse [`toTest`](#toTest) when you want to run the same tree through plain\n`elm-test` without the `elm-pages` wrapper CLI;\n[`toNamedSnapshots`](#toNamedSnapshots) flattens the tree for the visual\nrunner.\n\n@docs Test, test, describe, toTest, toNamedSnapshots\n\n\n## Other runners\n\nUse these when you don't want to wrap a single test in [`test`](#test) -\nfor example, in plain `elm-test` `\\() -> ...` bodies or when feeding the\nvisual runner directly. [`runSteps`](#runSteps) is the lower-level\nprimitive used to thread a list of steps through a `ProgramTest`\nwithout finalizing it; the generated `TestApp` module uses it to bake\nextras like a model inspector into its `start`.\n\n@docs expect, snapshots, runSteps, applyStep\n\n\n## Simulating user input\n\n`Test.ProgramTest` follows a philosophy (inspired by `elm-program-test`)\nof driving the browser in a way that mirrors how a user would interact\nwith your elm-pages app.\n\nThat gives us confidence that the scenarios in our automated tests\nmatch the real user experience. It also helps keep us honest about\nmaking our apps accessible and usable.\n\n@docs clickButton, clickButtonWith, clickLink, fillIn, fillInTextarea, check\n\n@docs selectOption\n\n@docs pressEnter, pressKey\n\n@docs simulateDomEvent\n\n\n## View assertions\n\n@docs ensureViewHas, ensureViewHasNot, ensureView\n\n\n## Scoping\n\n@docs withinFind\n\n\n## Grouping\n\n@docs group\n\n\n## Suite overview thumbnails\n\n@docs represent\n\n\n## Navigation\n\n@docs navigateTo, ensureBrowserUrl\n\n@docs ensureBrowserHistory\n\n\n## Simulating BackendTask responses\n\nWhen a `BackendTask` makes an HTTP request or calls a custom port, the\ntest pauses until you provide a simulated response. These functions work\nregardless of where the `BackendTask` originated -- route data loading,\nform actions, or BackendTask effects returned from `update`.\n\n@docs simulateHttpGet, simulateHttpPost, simulateHttpError\n\n@docs simulateCustom\n\n\n## Asserting on pending requests\n\n@docs ensureHttpGet, ensureHttpPost, ensureCustom\n\n\n## Subscriptions and incoming ports\n\n@docs withSimulatedSubscriptions, simulateIncomingPort\n\n\n## Snapshots\n\nSnapshots record the rendered view at each step of the test pipeline.\nThe browser visual test runner (`elm-pages dev` at `/_tests`) uses them\nto let you step through test execution in the browser.\n\n@docs Snapshot, withModelInspector\n\n\n## Starting a test\n\nThe generated `TestApp` module in your project wraps [`start`](#start) with\nyour app's config, so a typical test file writes:\n\n    suite : PagesProgram.Test\n    suite =\n        PagesProgram.describe \"Home\"\n            [ PagesProgram.test \"renders the greeting\"\n                (TestApp.start \"/my-page\" BackendTaskTest.init)\n                [ PagesProgram.ensureViewHas [ Selector.text \"Hello\" ] ]\n            ]\n\nFor tests that don't need a full elm-pages route module, use\n[`fromConfig`](#fromConfig) with a lightweight `{ data, init, update,\nview }` record instead.\n\n@docs start, fromConfig\n\n","unions":[{"name":"Test","comment":" A named tree of `ProgramTest` pipelines, mirroring `elm-test`'s `Test`.\nBuild leaves with [`test`](#test) and group them with\n[`describe`](#describe). The `elm-pages` test runner discovers a\n`Test.PagesProgram.Test`-typed value (typically named `suite`) in each\ntest module.\n","args":[],"cases":[]}],"aliases":[{"name":"ProgramTest","comment":" An in-progress elm-pages program test. Create one with\n[`start`](#start) (or [`fromConfig`](#fromConfig) for ad-hoc programs)\nand pass it to [`test`](#test) along with a list of [`Step`](#Step)\nvalues describing the user interactions and assertions.\n","args":["model","msg"],"type":"Test.PagesProgram.Internal.ProgramTest model msg"},{"name":"Snapshot","comment":" A snapshot of the program state at a point in the test pipeline.\nYou don't need to inspect this directly -- pass snapshots to\n[`Test.PagesProgram.Viewer.app`](Test-PagesProgram-Viewer#app) to view\nthem in the browser.\n","args":[],"type":"Test.PagesProgram.Internal.Snapshot"},{"name":"Step","comment":" A single step in a test pipeline - a click, a form fill, a\nsimulated HTTP response, an assertion, and so on. Steps are opaque\nvalues; build them with the helpers in this module\n([`clickButton`](#clickButton), [`ensureViewHas`](#ensureViewHas),\n[`simulateHttpGet`](#simulateHttpGet), and friends).\n\nUse [`group`](#group) and [`withinFind`](#withinFind) to nest a list of\nsteps inside a labelled scope.\n\n    incrementSteps : List (PagesProgram.Step model msg)\n    incrementSteps =\n        [ PagesProgram.ensureViewHas [ Selector.text \"Count: 0\" ]\n        , PagesProgram.clickButton \"+\"\n        , PagesProgram.ensureViewHas [ Selector.text \"Count: 1\" ]\n        ]\n\n","args":["model","msg"],"type":"Test.PagesProgram.Internal.Step model msg"}],"values":[{"name":"applyStep","comment":" Apply a single [`Step`](#Step) to a `ProgramTest`. Equivalent to\n[`runSteps`](#runSteps) with a singleton list. Useful when threading a\n`Step` value through a chain that already returns `ProgramTest`. End-user\ntests should prefer [`test`](#test) / [`expect`](#expect) over chaining\ndirectly.\n\n    PagesProgram.expect\n        (TestApp.start \"/\" BackendTaskTest.init\n            |> PagesProgram.applyStep\n                (PagesProgram.withModelInspector Debug.toString)\n        )\n        [ PagesProgram.ensureViewHas [ Selector.text \"Hello\" ] ]\n\n","type":"Test.PagesProgram.Step model msg -> Test.PagesProgram.ProgramTest model msg -> Test.PagesProgram.ProgramTest model msg"},{"name":"check","comment":" Simulate checking or unchecking a checkbox. The checkbox is found\nby its label text, just like a user would identify it on screen.\n\nSupports two common HTML patterns:\n\n1.  A separate `<label for=\"id\">` pointing to an `<input id=\"id\">`\n\n2.  A `<label>` wrapping both the checkbox and the label text\n\n```\nPagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n    [ PagesProgram.check \"I agree to the terms\" True\n    , PagesProgram.ensureViewHas [ Selector.text \"Terms accepted\" ]\n    ]\n```\n\n","type":"String.String -> Basics.Bool -> Test.PagesProgram.Step model msg"},{"name":"clickButton","comment":" Simulate clicking a button with the given text. Finds the first `<button>`\nelement whose text content matches, simulates a click event, and passes the\nresulting message through `update`.\n\n    PagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.clickButton \"+1\"\n        , PagesProgram.ensureViewHas [ Selector.text \"1\" ]\n        ]\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"clickButtonWith","comment":" Like [`clickButton`](#clickButton), but finds the button by CSS selectors\ninstead of text content. Useful for icon buttons (SVGs, empty buttons) that\nhave no text. Composes with [`withinFind`](#withinFind) to scope interactions.\n\n    import Test.Html.Selector as Selector exposing (class, tag)\n\n    -- Within the \"Buy milk\" todo, click its toggle checkbox\n    PagesProgram.expect (TestApp.start \"/\" setup)\n        [ PagesProgram.withinFind\n            [ tag \"li\", Selector.containing [ attribute (Attr.value \"Buy milk\") ] ]\n            [ PagesProgram.clickButtonWith [ class \"toggle\" ] ]\n        ]\n\nIf the button is inside a `<form>`, the form submit event is simulated\n(setting `useFetcher` correctly for concurrent forms). Otherwise a click\nevent is simulated.\n\n","type":"List.List Test.Html.Selector.Selector -> Test.PagesProgram.Step model msg"},{"name":"clickLink","comment":" Simulate clicking a link with the given text. The `href` is read\nfrom the rendered view automatically, just like a real browser.\n\n    PagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.clickLink \"About\"\n        , PagesProgram.ensureBrowserUrl\n            (\\url -> url |> String.contains \"/about\" |> Expect.true \"\")\n        ]\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"describe","comment":" Group related tests under a shared heading.\n\n    suite : PagesProgram.Test\n    suite =\n        PagesProgram.describe \"Blog\"\n            [ PagesProgram.test \"lists published posts\" blogIndexListsPostsTest\n            , PagesProgram.test \"hides drafts from the index\" draftsHiddenTest\n            ]\n\n`describe` blocks can nest arbitrarily.\n\n","type":"String.String -> List.List Test.PagesProgram.Test -> Test.PagesProgram.Test"},{"name":"ensureBrowserHistory","comment":" Assert on the browser URL history. The history is a list of URLs\nvisited during the test, most recent first. Use with framework-driven\ntests started with generated `TestApp.start`.\n\n    PagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.navigateTo \"/about\"\n        , PagesProgram.ensureBrowserHistory\n            (\\history ->\n                history\n                    |> List.length\n                    |> Expect.equal 2\n            )\n        ]\n\n","type":"(List.List String.String -> Expect.Expectation) -> Test.PagesProgram.Step model msg"},{"name":"ensureBrowserUrl","comment":" Assert on the current browser URL. Use with framework-driven tests\nstarted with generated `TestApp.start`, where navigation is tracked by the Platform.\n\n    PagesProgram.expect (TestApp.start \"/hello\" BackendTaskTest.init)\n        [ PagesProgram.ensureBrowserUrl\n            (\\url -> url |> Expect.equal \"https://localhost:1234/hello\")\n        ]\n\n","type":"(String.String -> Expect.Expectation) -> Test.PagesProgram.Step model msg"},{"name":"ensureCustom","comment":" Assert that a `BackendTask.Custom.run` call with the given port name\nis currently pending.\n\n    TestApp.start \"/\" BackendTaskTest.init\n        |> PagesProgram.ensureCustom \"getTodos\"\n        |> PagesProgram.simulateCustom \"getTodos\" response\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"ensureHttpGet","comment":" Assert that a GET request to the given URL is currently pending.\nUse this before `simulateHttpGet` to verify the right request was made.\n\n    TestApp.start \"/\" BackendTaskTest.init\n        |> PagesProgram.ensureHttpGet \"https://api.example.com/user\"\n        |> PagesProgram.simulateHttpGet \"https://api.example.com/user\" response\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"ensureHttpPost","comment":" Assert that a POST request to the given URL is currently pending.\n\n    TestApp.start \"/\" BackendTaskTest.init\n        |> PagesProgram.ensureHttpPost \"https://api.example.com/submit\"\n        |> PagesProgram.simulateHttpPost \"https://api.example.com/submit\" response\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"ensureView","comment":" Assert on the rendered view using a custom assertion function.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.ensureView\n            (\\query ->\n                query\n                    |> Query.find [ Selector.tag \"h1\" ]\n                    |> Query.has [ Selector.text \"Title\" ]\n            )\n        ]\n\n","type":"(Test.Html.Query.Single msg -> Expect.Expectation) -> Test.PagesProgram.Step model msg"},{"name":"ensureViewHas","comment":" Assert that the rendered view contains elements matching all the given\nselectors. The test continues after this assertion - use it inline within\na step list.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.ensureViewHas [ Selector.text \"Welcome\" ]\n        , PagesProgram.ensureViewHas [ Selector.id \"main-content\" ]\n        ]\n\n","type":"List.List Test.Html.Selector.Selector -> Test.PagesProgram.Step model msg"},{"name":"ensureViewHasNot","comment":" Assert that the rendered view does NOT contain elements matching the\ngiven selectors.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.ensureViewHasNot [ Selector.text \"Error\" ] ]\n\n","type":"List.List Test.Html.Selector.Selector -> Test.PagesProgram.Step model msg"},{"name":"expect","comment":" Run a list of [`Step`](#Step)s against a starting `ProgramTest` and\nfinalize the result as an `Expect.Expectation`. Use this when writing\ntests in plain `elm-test` style:\n\n    import Expect\n    import Test exposing (Test, test)\n    import Test.PagesProgram as PagesProgram\n\n    suite : Test\n    suite =\n        test \"renders the greeting\" <|\n            \\() ->\n                PagesProgram.expect\n                    (TestApp.start \"/\" BackendTaskTest.init)\n                    [ PagesProgram.ensureViewHas [ Selector.text \"Hello\" ] ]\n\n","type":"Test.PagesProgram.ProgramTest model msg -> List.List (Test.PagesProgram.Step model msg) -> Expect.Expectation"},{"name":"fillIn","comment":" Simulate typing text into an input field.\n\nFor elm-pages forms, pass the form ID (from `Form.options`) and the field\nname (from `Form.field`):\n\n    PagesProgram.expect (TestApp.start \"/feedback\" BackendTaskTest.init)\n        [ PagesProgram.fillIn \"feedback-form\" \"message\" \"Hello!\"\n        , PagesProgram.clickButton \"Submit Feedback\"\n        ]\n\nFor plain inputs, pass the element's `id` attribute and label text:\n\n    PagesProgram.fillIn \"email\" \"Email address\" \"alice@example.com\"\n\nPass empty string as the first argument to find inputs nested inside\n`<label>` elements without an explicit `id`:\n\n    PagesProgram.fillIn \"\" \"Username\" \"alice\"\n\n","type":"String.String -> String.String -> String.String -> Test.PagesProgram.Step model msg"},{"name":"fillInTextarea","comment":" Fill in a textarea with the given content. Finds the first `<textarea>`\nelement in the view (or within the current `within` scope) and simulates\nan input event.\n\n    PagesProgram.fillInTextarea \"Hello, world!\"\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"fromConfig","comment":" Build a `ProgramTest` from a lightweight `{ data, init, update, view }`\nrecord, without going through the generated `TestApp` module. Useful for\ntests of components or for the documentation/examples in this package\nthat demonstrate the test API without a full elm-pages route module.\n\nFor application code with a real elm-pages route, prefer [`start`](#start)\nthrough the generated `TestApp` module.\n\n    import BackendTask\n    import Html\n    import Test.PagesProgram as PagesProgram\n\n    blogPage : { data : BackendTask.BackendTask err (), init : () -> ( {}, List a ), update : msg -> {} -> ( {}, List a ), view : () -> {} -> { title : String, body : List (Html.Html msg) } }\n    blogPage =\n        { data = BackendTask.succeed ()\n        , init = \\() -> ( {}, [] )\n        , update = \\_ model -> ( model, [] )\n        , view = \\_ _ -> { title = \"Blog\", body = [ Html.text \"Hello\" ] }\n        }\n\n    test : PagesProgram.Test\n    test =\n        PagesProgram.test \"renders the greeting\"\n            (PagesProgram.fromConfig blogPage)\n            [ PagesProgram.ensureViewHas [] ]\n\n","type":"{ data : BackendTask.BackendTask FatalError.FatalError data, init : data -> ( model, List.List (BackendTask.BackendTask FatalError.FatalError msg) ), update : msg -> model -> ( model, List.List (BackendTask.BackendTask FatalError.FatalError msg) ), view : data -> model -> { title : String.String, body : List.List (Html.Html msg) } } -> Test.PagesProgram.ProgramTest model msg"},{"name":"group","comment":" Group a sequence of test steps under a named label. The visual test\nrunner renders groups as collapsible sections in the command log sidebar.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.group \"Log in\"\n            [ PagesProgram.fillIn \"email\" \"Email\" \"alice@example.com\"\n            , PagesProgram.fillIn \"password\" \"Password\" \"secret\"\n            , PagesProgram.clickButton \"Log In\"\n            ]\n        , PagesProgram.group \"Create todos\"\n            [ PagesProgram.fillIn \"new-todo\" \"description\" \"Buy milk\"\n            , PagesProgram.clickButton \"Add\"\n            ]\n        ]\n\n","type":"String.String -> List.List (Test.PagesProgram.Step model msg) -> Test.PagesProgram.Step model msg"},{"name":"navigateTo","comment":" Navigate directly to a URL path. In framework-driven tests, this triggers\nthe full Platform navigation cycle: `LinkClicked` -> `UrlChanged` ->\ndata loading -> re-render.\n\n    PagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.navigateTo \"/counter\"\n        , PagesProgram.ensureViewHas [ Selector.text \"Count: 0\" ]\n        ]\n\n","type":"String.String -> Test.PagesProgram.Step model msg"},{"name":"pressEnter","comment":" Simulate the user pressing Enter while focus is on the matched\nelement. Faithful to the browser's default-action behavior:\n\n1.  Fires `keydown` with `key: \"Enter\"` on the matched element.\n    Any `onKeyDown` handler runs.\n2.  If the matched element sits inside a `<form>`, dispatches the\n    form's `submit` event afterward (mirroring the browser default\n    for Enter inside a form's text input). If the input isn't in a\n    form, step 2 is skipped.\n\nThis is the right primitive for \"submit a form that has no submit\nbutton\" — the dominant TodoMVC / inline-edit pattern.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.fillIn \"new-todo\" \"new-todo\" \"Buy milk\"\n        , PagesProgram.pressEnter [ Selector.id \"new-todo\" ]\n        , PagesProgram.ensureViewHas [ Selector.text \"Buy milk\" ]\n        ]\n\n","type":"List.List Test.Html.Selector.Selector -> Test.PagesProgram.Step model msg"},{"name":"pressKey","comment":" Dispatch a `keydown` event with the given `key` (e.g., `\"Enter\"`,\n`\"Escape\"`, `\"ArrowDown\"`) on the first element matching `selectors`.\n\nLow-level primitive — fires `keydown` and stops there. No browser\ndefault-action emulation. If you want the \"Enter pressed inside a form\nsubmits the form\" behavior, use [`pressEnter`](#pressEnter) instead.\n\nIf the matched element has no `onKeyDown` handler, this is a no-op\n(no error). That mirrors the browser: a keydown without a listener\njust bubbles past silently.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.fillIn \"search\" \"search\" \"elm-pages\"\n        , PagesProgram.pressKey \"Escape\" [ Selector.id \"search\" ]\n        , PagesProgram.ensureViewHas [ Selector.text \"Search cleared\" ]\n        ]\n\n","type":"String.String -> List.List Test.Html.Selector.Selector -> Test.PagesProgram.Step model msg"},{"name":"represent","comment":" Mark the most recent snapshot as the representative thumbnail for\nthis test in the suite overview. By default the overview picks the\nlast snapshot (or the failing snapshot if the test fails); use\n`represent` after the assertion you want featured.\n\n    PagesProgram.test \"adds a Café Latte to the bag\"\n        (TestApp.start \"/menu\" BackendTaskTest.init)\n        [ PagesProgram.clickButton \"Add to bag\"\n        , PagesProgram.ensureViewHas [ Selector.class \"bag-count\", Selector.text \"1\" ]\n        , PagesProgram.represent\n        ]\n\nIf the test fails, the failing step always wins regardless of any\n`represent` calls — failures are forced visible.\n\n","type":"Test.PagesProgram.Step model msg"},{"name":"runSteps","comment":" Run a list of [`Step`](#Step)s against a `ProgramTest`, returning\nthe new `ProgramTest`. Use this when you need to apply steps without\nfinalizing the test (e.g., to bake setup into the result of a custom\n`start`); end-of-test finalization is what [`test`](#test),\n[`expect`](#expect), and [`snapshots`](#snapshots) do for you.\n\n    PagesProgram.runSteps\n        [ PagesProgram.simulateHttpGet \"https://api.example.com/counter\" counterDataResponse\n        , PagesProgram.ensureViewHas [ Selector.text \"Count: 0\" ]\n        ]\n        (TestApp.start \"/counter\" BackendTaskTest.init)\n\n","type":"List.List (Test.PagesProgram.Step model msg) -> Test.PagesProgram.ProgramTest model msg -> Test.PagesProgram.ProgramTest model msg"},{"name":"selectOption","comment":" Select an option from a dropdown `<select>` element. The framework\nfinds the `<select>` by its label text and the `<option>` by its display\ntext -- just what the user sees, no IDs or internal values needed.\n\n    PagesProgram.expect (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.selectOption \"Favorite Color\" \"Blue\"\n        , PagesProgram.ensureViewHas [ Selector.text \"Selected: blue\" ]\n        ]\n\n","type":"String.String -> String.String -> Test.PagesProgram.Step model msg"},{"name":"simulateCustom","comment":" Simulate a pending `BackendTask.Custom.run` call resolving with the given\nJSON response. Provide the port name and the JSON value the port would return.\n\n    TestApp.start \"/\" setup\n        |> PagesProgram.simulateCustom \"getTodos\"\n            (Encode.list todoEncoder myTodos)\n        |> PagesProgram.ensureViewHas [ Selector.text \"Buy milk\" ]\n\n","type":"String.String -> Json.Encode.Value -> Test.PagesProgram.Step model msg"},{"name":"simulateDomEvent","comment":" Simulate a DOM event on a targeted element. This is the escape hatch\nfor events not covered by `clickButton`, `fillIn`, etc.\n\nThe first argument narrows the query to find the target element.\nThe second argument is the event to simulate (from `Test.Html.Event`).\n\n    import Test.Html.Event as Event\n\n    PagesProgram.expect myTest\n        [ PagesProgram.simulateDomEvent\n            (Query.find [ Selector.id \"my-input\" ])\n            Event.focus\n        ]\n\n","type":"(Test.Html.Query.Single msg -> Test.Html.Query.Single msg) -> ( String.String, Json.Encode.Value ) -> Test.PagesProgram.Step model msg"},{"name":"simulateHttpError","comment":" Simulate an HTTP error (network error or timeout) on a pending request.\nUse with `Test.BackendTask.HttpError`:\n\n    import Test.BackendTask as BackendTaskTest exposing (HttpError(..))\n\n    TestApp.start \"/\" BackendTaskTest.init\n        |> PagesProgram.simulateHttpError \"GET\" \"https://api.example.com/data\" NetworkError\n\n","type":"String.String -> String.String -> Test.BackendTask.HttpError -> Test.PagesProgram.Step model msg"},{"name":"simulateHttpGet","comment":" Provide a simulated JSON response for a pending HTTP GET request.\nWorks for any pending `BackendTask.Http` request -- route data loading,\nform actions, or BackendTask effects returned from `update`.\n\n    TestApp.start \"/\" BackendTaskTest.init\n        |> PagesProgram.simulateHttpGet\n            \"https://api.example.com/user\"\n            (Encode.object [ ( \"name\", Encode.string \"Alice\" ) ])\n        |> PagesProgram.ensureViewHas [ Selector.text \"Alice\" ]\n\n","type":"String.String -> Json.Encode.Value -> Test.PagesProgram.Step model msg"},{"name":"simulateHttpPost","comment":" Simulate a pending HTTP POST request resolving with the given JSON response\nbody.\n","type":"String.String -> Json.Encode.Value -> Test.PagesProgram.Step model msg"},{"name":"simulateIncomingPort","comment":" Simulate data arriving through an incoming port. The test framework\nevaluates the subscription function (registered via `withSimulatedSubscriptions`)\nwith the current model, finds matching port subscriptions, decodes the value,\nand dispatches the resulting message through `update`.\n\n    PagesProgram.simulateIncomingPort \"websocketData\"\n        (Encode.string \"hello from server\")\n\nFails if:\n\n  - `withSimulatedSubscriptions` was not called\n  - The program is not currently subscribed to the named port\n  - The value does not match the port's decoder\n\n","type":"String.String -> Json.Encode.Value -> Test.PagesProgram.Step model msg"},{"name":"snapshots","comment":" Run a list of [`Step`](#Step)s against a starting `ProgramTest` and\nreturn the recorded snapshots. Use this when feeding the visual test\nrunner directly without going through [`test`](#test):\n\n    import Test.PagesProgram as PagesProgram\n    import Test.PagesProgram.Viewer as Viewer\n\n    main : Program Viewer.Flags Viewer.Model Viewer.Msg\n    main =\n        Viewer.app\n            [ ( \"Counter increments\"\n              , PagesProgram.snapshots startCounter\n                    [ PagesProgram.clickButton \"+\"\n                    , PagesProgram.ensureViewHas [ Selector.text \"Count: 1\" ]\n                    ]\n              )\n            ]\n\n","type":"Test.PagesProgram.ProgramTest model msg -> List.List (Test.PagesProgram.Step model msg) -> List.List Test.PagesProgram.Snapshot"},{"name":"start","comment":" Start a full-fidelity elm-pages test by driving `Pages.Internal.Platform`\ndirectly. The generated `TestApp` module wraps this with your app's\n`Main.config`, so the typical usage is:\n\n    PagesProgram.expect\n        (TestApp.start \"/\" BackendTaskTest.init)\n        [ PagesProgram.ensureViewHas [ Selector.text \"Hello\" ] ]\n\nWhere the generated `TestApp.start` is `PagesProgram.start Main.config`.\n\nYou can seed the initial incoming request through [`Test.BackendTask`](Test-BackendTask)\nusing [`withRequestCookie`](Test-BackendTask#withRequestCookie),\n[`withRequestHeader`](Test-BackendTask#withRequestHeader), and\n[`withRequestTime`](Test-BackendTask#withRequestTime).\n\nBackendTask resolution uses the `Test.BackendTask` virtual filesystem, so\nfile reads, env vars, time, and other auto-resolvable BackendTasks work\nout of the box. File writes in actions automatically update the virtual FS,\nand subsequent data resolution sees the updated files.\n\n","type":"(effect -> Test.PagesProgram.SimulatedEffect.SimulatedEffect userMsg) -> Pages.Internal.Platform.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> String.String -> Test.BackendTask.Internal.TestSetup -> Test.PagesProgram.ProgramTest { platformModel : Pages.Internal.Platform.Model userModel pageData actionData sharedData, virtualFs : Test.BackendTask.Internal.VirtualFS, cookieJar : Test.PagesProgram.CookieJar.CookieJar, pendingDataError : Maybe.Maybe String.String, pendingDataPath : Maybe.Maybe String.String, pendingActionBody : Maybe.Maybe { body : String.String, path : String.String } } (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"test","comment":" Name a single test from a starting `ProgramTest` and a list of\n[`Step`](#Step)s. The framework runs the steps and finalizes the test\nfor you.\n\n    suite : PagesProgram.Test\n    suite =\n        PagesProgram.test \"lists published posts\"\n            (TestApp.start \"/blog\" baseSetup)\n            [ PagesProgram.ensureViewHas [ Selector.text \"Hello, World\" ] ]\n\nUnlike module-level binding names, the string can be a full sentence\nand is what shows up in `elm-test` output and in the visual runner\nsidebar.\n\n","type":"String.String -> Test.PagesProgram.ProgramTest model msg -> List.List (Test.PagesProgram.Step model msg) -> Test.PagesProgram.Test"},{"name":"toNamedSnapshots","comment":" Flatten a named test tree into named snapshot sequences.\n\nThe visual test runner renders this list directly: each entry's name is\nthe slash-joined path of enclosing `describe` labels and the leaf\n`test` name (e.g. `\"Blog / lists published posts\"`).\n\n    snapshotNames : List String\n    snapshotNames =\n        myTests\n            |> PagesProgram.toNamedSnapshots\n            |> List.map Tuple.first\n\n","type":"Test.PagesProgram.Test -> List.List ( String.String, List.List Test.PagesProgram.Snapshot )"},{"name":"toTest","comment":" Convert a named `Test.PagesProgram.Test` tree into an\n`elm-explorations/test` `Test`.\n\n`elm-pages test` works with `Test.PagesProgram.Test` directly. Use\n`toTest` when you want to plug the same tree into a plain `elm-test`\nrunner without the `elm-pages` wrapper CLI.\n\n    import Test\n    import Test.PagesProgram as PagesProgram\n\n    suite : Test.Test\n    suite =\n        PagesProgram.toTest myTests\n\n","type":"Test.PagesProgram.Test -> Test.Test"},{"name":"withModelInspector","comment":" Enable model state inspection in snapshots. Pass `Debug.toString`\n(or any `model -> String` function) once at the starting `ProgramTest`,\nand every snapshot recorded in the run will include the model state.\n\nThis is configuration on the starting program, not a step in the test\nlist - call it on the value passed as the first argument to\n[`test`](#test) / [`expect`](#expect) / [`snapshots`](#snapshots):\n\n    PagesProgram.test \"increments\"\n        (TestApp.start \"/counter\" BackendTaskTest.init\n            |> PagesProgram.withModelInspector Debug.toString\n        )\n        [ PagesProgram.clickButton \"+1\"\n        , PagesProgram.ensureViewHas [ Selector.text \"Count: 1\" ]\n        ]\n\nSince published packages cannot use `Debug.toString` directly, you must\npass it from your own test code.\n\n","type":"(model -> String.String) -> Test.PagesProgram.ProgramTest model msg -> Test.PagesProgram.ProgramTest model msg"},{"name":"withSimulatedSubscriptions","comment":" Register a simulated subscriptions function. The function is called with\nthe current model each time `simulateIncomingPort` is used, so subscriptions\ncan be model-dependent (e.g., only listen when connected).\n\n    import Test.PagesProgram.SimulatedSub as SimulatedSub\n\n    PagesProgram.expect myTest\n        [ PagesProgram.withSimulatedSubscriptions\n            (\\model ->\n                if model.isConnected then\n                    SimulatedSub.port_ \"websocketData\"\n                        (Decode.string |> Decode.map GotMessage)\n\n                else\n                    SimulatedSub.none\n            )\n        ]\n\n","type":"(model -> Test.PagesProgram.SimulatedSub.SimulatedSub msg) -> Test.PagesProgram.Step model msg"},{"name":"withinFind","comment":" Scope interactions and assertions to a specific part of the DOM.\nThe first argument narrows the query via `Query.find`, and the second is\nthe interaction to perform within that scope.\n\n    PagesProgram.expect myTest\n        [ PagesProgram.withinFind\n            [ Selector.class \"todo-count\" ]\n            [ PagesProgram.ensureViewHas [ Selector.text \"2\" ] ]\n        ]\n\nThe scope label appears in assertion labels, e.g.,\n`ensureViewHas text \"2\" (within .todo-count)`.\n\nThe scope container is also highlighted with a dashed border in the preview.\nThe scope is reset after the inner steps run; nest `withinFind` calls to\ndrill into child scopes.\n\n","type":"List.List Test.Html.Selector.Selector -> List.List (Test.PagesProgram.Step model msg) -> Test.PagesProgram.Step model msg"}],"binops":[]},{"name":"Test.PagesProgram.CookieJar","comment":" A cookie jar used to seed the initial request of a test and to track\ncookies across subsequent requests. Supports:\n\n  - Setting/getting individual cookies\n  - Seeding a signed session cookie via [`setSession`](#setSession)\n  - Parsing `Set-Cookie` response headers to capture cookies and their\n    attributes (Path, Domain, Expires, Max-Age, Secure, HttpOnly, SameSite)\n    across requests\n  - Applying the jar to a [`Test.BackendTask.TestSetup`](Test-BackendTask#TestSetup)\n    via [`withCookies`](#withCookies)\n\n@docs CookieJar\n@docs CookieEntry\n\n\n## Building\n\n@docs init, set, setSession, get, getEntry, toDict, entries\n\n\n## Capturing from responses\n\n@docs withSetCookieHeaders\n\n\n## Seeding a test\n\n@docs withCookies\n\n","unions":[{"name":"CookieJar","comment":" A collection of cookies maintained across HTTP requests.\n","args":[],"cases":[]}],"aliases":[{"name":"CookieEntry","comment":" A cookie with its value and attributes as parsed from `Set-Cookie`.\n\nAttributes default to unset/false when a cookie is added via [`set`](#set) or\n[`setSession`](#setSession). [`withSetCookieHeaders`](#withSetCookieHeaders)\npopulates attributes from response headers.\n\n`expires` is preserved as the raw header string (date formats vary). `sameSite`\nis the raw attribute value (`\"Strict\"`, `\"Lax\"`, or `\"None\"`).\n\n","args":[],"type":"{ value : String.String, path : Maybe.Maybe String.String, domain : Maybe.Maybe String.String, expires : Maybe.Maybe String.String, maxAge : Maybe.Maybe Basics.Int, secure : Basics.Bool, httpOnly : Basics.Bool, sameSite : Maybe.Maybe String.String }"}],"values":[{"name":"entries","comment":" List every cookie in the jar with its full entry.\n","type":"Test.PagesProgram.CookieJar.CookieJar -> List.List ( String.String, Test.PagesProgram.CookieJar.CookieEntry )"},{"name":"get","comment":" Get a cookie's value from the jar.\n","type":"String.String -> Test.PagesProgram.CookieJar.CookieJar -> Maybe.Maybe String.String"},{"name":"getEntry","comment":" Get a cookie's full entry (value + attributes) from the jar.\n","type":"String.String -> Test.PagesProgram.CookieJar.CookieJar -> Maybe.Maybe Test.PagesProgram.CookieJar.CookieEntry"},{"name":"init","comment":" An empty cookie jar, ready for [`set`](#set) and\n[`setSession`](#setSession).\n","type":"Test.PagesProgram.CookieJar.CookieJar"},{"name":"set","comment":" Set a cookie in the jar with the given name and value. Attributes default\nto unset/false; use [`withSetCookieHeaders`](#withSetCookieHeaders) to capture\nattributes from a response.\n","type":"String.String -> String.String -> Test.PagesProgram.CookieJar.CookieJar -> Test.PagesProgram.CookieJar.CookieJar"},{"name":"setSession","comment":" Set a signed session cookie in the jar. The [`Session`](Test-PagesProgram-Session#Session)\nvalue is encoded and signed the same way\n[`Server.Session`](Server-Session) encodes its payload at runtime, so routes\nreading the cookie get the seeded values back out.\n\n    import Test.PagesProgram.CookieJar as CookieJar\n    import Test.PagesProgram.Session as Session\n\n    CookieJar.init\n        |> CookieJar.setSession\n            { name = \"mysession\"\n            , secret = \"test-secret\"\n            , session =\n                Session.init\n                    |> Session.withValue \"userId\" \"42\"\n            }\n\nThe `secret` is recorded alongside the signed cookie so the visual test\nrunner can surface which secret produced each signed cookie. The test mock\ndoes not use real HMAC, so `secret` may be any non-empty string **without `.`\ncharacters**.\n\n","type":"{ name : String.String, secret : String.String, session : Test.PagesProgram.Session.Session } -> Test.PagesProgram.CookieJar.CookieJar -> Test.PagesProgram.CookieJar.CookieJar"},{"name":"toDict","comment":" Convert the cookie jar to a Dict of name/value pairs for use in request\nconstruction. Attributes are dropped — requests only need name/value.\n","type":"Test.PagesProgram.CookieJar.CookieJar -> Dict.Dict String.String String.String"},{"name":"withCookies","comment":" Apply the jar to a [`Test.BackendTask.TestSetup`](Test-BackendTask#TestSetup),\nseeding every cookie in the jar on the initial request.\n\n    import Test.BackendTask as BackendTaskTest\n    import Test.PagesProgram.CookieJar as CookieJar\n\n    BackendTaskTest.init\n        |> CookieJar.withCookies\n            (CookieJar.init\n                |> CookieJar.set \"theme\" \"dark\"\n            )\n\n","type":"Test.PagesProgram.CookieJar.CookieJar -> Test.BackendTask.Internal.TestSetup -> Test.BackendTask.Internal.TestSetup"},{"name":"withSetCookieHeaders","comment":" Apply a list of Set-Cookie header values to the cookie jar. Parses each\nheader to extract the cookie name, value, and attributes (`Path`, `Domain`,\n`Expires`, `Max-Age`, `Secure`, `HttpOnly`, `SameSite`). Unknown attributes\nare ignored.\n\n    CookieJar.init\n        |> CookieJar.withSetCookieHeaders\n            [ \"session=abc123; Path=/; HttpOnly\"\n            , \"theme=dark\"\n            ]\n        |> CookieJar.get \"session\"\n        -- Just \"abc123\"\n\n","type":"List.List String.String -> Test.PagesProgram.CookieJar.CookieJar -> Test.PagesProgram.CookieJar.CookieJar"}],"binops":[]},{"name":"Test.PagesProgram.Session","comment":" Build a session payload to seed into a signed session cookie. Pair with\n[`Test.PagesProgram.CookieJar.setSession`](Test-PagesProgram-CookieJar#setSession)\nto place the signed value into the cookie jar for a test.\n\n    import Test.BackendTask as BackendTaskTest\n    import Test.PagesProgram.CookieJar as CookieJar\n    import Test.PagesProgram.Session as Session\n\n    BackendTaskTest.init\n        |> CookieJar.withCookies\n            (CookieJar.init\n                |> CookieJar.setSession\n                    { name = \"mysession\"\n                    , secret = \"test-secret\"\n                    , session =\n                        Session.init\n                            |> Session.withValue \"userId\" \"42\"\n                            |> Session.withFlash \"greeting\" \"Welcome back!\"\n                    }\n            )\n\n@docs Session\n\n\n## Building\n\n@docs init, withValue, withFlash\n\n","unions":[],"aliases":[{"name":"Session","comment":" A session payload: a set of persistent session values and one-shot flash\nvalues. Mirrors the shape produced by [`Server.Session`](Server-Session) at\nruntime.\n","args":[],"type":"Test.BackendTask.Internal.Session"}],"values":[{"name":"init","comment":" An empty session with no values, ready for [`withValue`](#withValue) and\n[`withFlash`](#withFlash).\n","type":"Test.PagesProgram.Session.Session"},{"name":"withFlash","comment":" Add a flash session value. Flash values are available on the next request\nonly, matching [`Server.Session.withFlash`](Server-Session#withFlash).\n","type":"String.String -> String.String -> Test.PagesProgram.Session.Session -> Test.PagesProgram.Session.Session"},{"name":"withValue","comment":" Add a persistent session value.\n","type":"String.String -> String.String -> Test.PagesProgram.Session.Session -> Test.PagesProgram.Session.Session"}],"binops":[]},{"name":"Test.PagesProgram.SimulatedEffect","comment":" Simulated effects for testing. These parallel Elm's `Cmd` type\nbut are inspectable by the test framework.\n\nUse these with [`Effect.testPerform`](Effect#testPerform) to decompose\nyour `Effect` type into something the test framework can process.\n\n    import Test.PagesProgram.SimulatedEffect as SimulatedEffect\n\n\n    -- In your Effect module:\n    testPerform : Effect msg -> SimulatedEffect msg\n    testPerform effect =\n        case effect of\n            None ->\n                SimulatedEffect.none\n\n            Cmd _ ->\n                -- Opaque Cmds cannot be simulated. Use simulateMsg\n                -- in your test to inject the message manually.\n                SimulatedEffect.none\n\n            Batch list ->\n                SimulatedEffect.batch (List.map testPerform list)\n\n            SendMsg msg ->\n                SimulatedEffect.dispatchMsg msg\n\n@docs SimulatedEffect\n\n@docs none, batch, dispatchMsg, setField, submitFetcher\n\n@docs map\n\n","unions":[{"name":"SimulatedEffect","comment":" A simulated effect that the test framework can inspect and execute.\n\n  - `None` -- no effect\n  - `Batch` -- multiple effects\n  - `DispatchMsg msg` -- dispatch a message through the update cycle\n  - `SetField` -- set a form field value\n  - `SubmitFetcher` -- submit a fetcher (concurrent form submission)\n\n","args":["msg"],"cases":[["None",[]],["Batch",["List.List (Test.PagesProgram.SimulatedEffect.SimulatedEffect msg)"]],["DispatchMsg",["msg"]],["SetField",["{ formId : String.String, name : String.String, value : String.String }"]],["SubmitFetcher",["Pages.Fetcher.Fetcher msg"]]]}],"aliases":[],"values":[{"name":"batch","comment":" Combine multiple simulated effects. Parallels `Effect.batch`.\n","type":"List.List (Test.PagesProgram.SimulatedEffect.SimulatedEffect msg) -> Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"},{"name":"dispatchMsg","comment":" Dispatch a message through the program's update cycle. This is the\ntest-simulatable equivalent of `Effect.sendMsg`.\n\n    testPerform effect =\n        case effect of\n            SendMsg msg ->\n                SimulatedEffect.dispatchMsg msg\n\n","type":"msg -> Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"},{"name":"map","comment":" Transform the messages produced by a simulated effect.\n","type":"(a -> msg) -> Test.PagesProgram.SimulatedEffect.SimulatedEffect a -> Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"},{"name":"none","comment":" No effect. Parallels `Effect.none`.\n\nAlso use this for opaque `Cmd` values that cannot be simulated.\nUse [`simulateMsg`](Test-PagesProgram#simulateMsg) in your test to\ninject the message that the `Cmd` would have produced.\n\n","type":"Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"},{"name":"setField","comment":" Set a form field value. Parallels `Effect.setField`.\n","type":"{ formId : String.String, name : String.String, value : String.String } -> Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"},{"name":"submitFetcher","comment":" Submit a fetcher for concurrent form submission. Parallels `Effect.submitFetcher`.\n","type":"Pages.Fetcher.Fetcher msg -> Test.PagesProgram.SimulatedEffect.SimulatedEffect msg"}],"binops":[]},{"name":"Test.PagesProgram.SimulatedSub","comment":" Simulated subscriptions for testing. These parallel Elm's `Sub` type\nbut are inspectable by the test framework.\n\nUse these with [`PagesProgram.withSimulatedSubscriptions`](Test-PagesProgram#withSimulatedSubscriptions)\nto register subscriptions, then send data through them with\n[`PagesProgram.simulateIncomingPort`](Test-PagesProgram#simulateIncomingPort).\n\n    import Test.PagesProgram.SimulatedSub as SimulatedSub\n\n    -- In your test setup:\n    |> PagesProgram.withSimulatedSubscriptions\n        (\\model ->\n            if model.isConnected then\n                SimulatedSub.port_ \"websocketData\"\n                    (Decode.string |> Decode.map GotMessage)\n            else\n                SimulatedSub.none\n        )\n\n@docs SimulatedSub\n\n@docs none, batch, port_\n\n@docs map\n\n","unions":[{"name":"SimulatedSub","comment":" A simulated subscription that the test framework can inspect and trigger.\n","args":["msg"],"cases":[["NoneSub",[]],["BatchSub",["List.List (Test.PagesProgram.SimulatedSub.SimulatedSub msg)"]],["PortSub",["String.String","Json.Decode.Decoder msg"]]]}],"aliases":[],"values":[{"name":"batch","comment":" Combine multiple subscriptions. Parallels `Sub.batch`.\n","type":"List.List (Test.PagesProgram.SimulatedSub.SimulatedSub msg) -> Test.PagesProgram.SimulatedSub.SimulatedSub msg"},{"name":"map","comment":" Transform the messages produced by a subscription.\n","type":"(a -> msg) -> Test.PagesProgram.SimulatedSub.SimulatedSub a -> Test.PagesProgram.SimulatedSub.SimulatedSub msg"},{"name":"none","comment":" No subscription. Parallels `Sub.none`.\n","type":"Test.PagesProgram.SimulatedSub.SimulatedSub msg"},{"name":"port_","comment":" Subscribe to an incoming port. Provide the port name and a decoder\nthat produces your message type.\n\n    SimulatedSub.port_ \"websocketData\"\n        (Decode.string |> Decode.map GotMessage)\n\n","type":"String.String -> Json.Decode.Decoder msg -> Test.PagesProgram.SimulatedSub.SimulatedSub msg"}],"binops":[]},{"name":"Test.PagesProgram.Viewer","comment":" A browser-based visual test runner that lets you step through elm-pages\ntest snapshots in your browser. Displays a Cypress-style command log sidebar\nalongside the rendered page view.\n\n    module TestViewer exposing (main)\n\n    import Test.PagesProgram as PagesProgram\n    import Test.PagesProgram.Viewer as Viewer\n\n    main =\n        Viewer.app\n            [ ( \"Blog loads posts\"\n              , myBlogTest |> PagesProgram.toSnapshots\n              )\n            , ( \"Counter increments\"\n              , myCounterTest |> PagesProgram.toSnapshots\n              )\n            ]\n\nOpen the compiled HTML in your browser, then use arrow keys to step through.\n\nThe Cookies sidebar (`c`) displays signed session cookies with the secret\nthat was used. Note: the in-memory \"checksum\" shown next to the secret is a\nfast non-cryptographic FNV1a hash, not a real HMAC-SHA256 — it only exists\nso the visual runner can distinguish different secrets and detect\ntest-time tampering of signed-cookie payloads.\n\n@docs app, Flags, Model, Msg\n\n","unions":[{"name":"Msg","comment":" The viewer's messages.\n","args":[],"cases":[]}],"aliases":[{"name":"Flags","comment":" Flags for the viewer app. Currently unused.\n","args":[],"type":"()"},{"name":"Model","comment":" The viewer's model.\n","args":[],"type":"{ tests : List.List Test.PagesProgram.Viewer.NamedTest, currentTestIndex : Basics.Int, currentStepIndex : Basics.Int, hoveredStepIndex : Maybe.Maybe Basics.Int, showModel : Basics.Bool, sidebarMode : Test.PagesProgram.Viewer.SidebarMode, navKey : Browser.Navigation.Key, basePath : String.String, searchQuery : String.String, viewportWidth : Maybe.Maybe Basics.Int, showEffects : Basics.Bool, showNetwork : Basics.Bool, showNetworkBackend : Basics.Bool, showNetworkFrontend : Basics.Bool, showFetchers : Basics.Bool, showCookies : Basics.Bool, previewMode : Test.PagesProgram.Viewer.PreviewMode, expandedGroups : Set.Set Basics.Int, modelTreeExpanded : Set.Set String.String }"}],"values":[{"name":"app","comment":" Create a visual test runner application. Pass a list of named tests with\ntheir snapshots.\n\n    main =\n        Viewer.app\n            [ ( \"my test\", myTest |> PagesProgram.toSnapshots )\n            ]\n\n","type":"List.List ( String.String, List.List Test.PagesProgram.Internal.Snapshot ) -> Platform.Program Test.PagesProgram.Viewer.Flags Test.PagesProgram.Viewer.Model Test.PagesProgram.Viewer.Msg"}],"binops":[]},{"name":"Test.Tui","comment":" Test a `Tui.program` the same way a user uses it: start the app, simulate\nterminal events, and assert on the screen or model. Inspired by\n[`elm-program-test`](https://package.elm-lang.org/packages/avh4/elm-program-test/latest/).\n\nTypical flow:\n\n  - Start with [`start`](#start) or [`startWithContext`](#startWithContext).\n  - Simulate input with [`pressKey`](#pressKey), [`clickText`](#clickText),\n    [`paste`](#paste), [`resize`](#resize), or [`advanceTime`](#advanceTime).\n  - Resolve pending `BackendTask` effects with [`resolveEffect`](#resolveEffect)\n    for the common case, or [`resolveEffectWith`](#resolveEffectWith) when you\n    need custom HTTP/command simulation.\n  - Finish with [`expectRunning`](#expectRunning), [`expectExit`](#expectExit),\n    or [`expectExitWith`](#expectExitWith).\n\nThe same named tests can be:\n\n  - Run headlessly through [`toTest`](#toTest) with `elm-test`\n  - Visualized through `elm-pages test`, which reads the same `TuiTest.Test`\n    value and shows the recorded snapshots in the terminal stepper\n\n```elm\nimport BackendTask\nimport Test\nimport Test.BackendTask as BackendTaskTest\nimport Test.Tui as TuiTest\nimport Tui\nimport Tui.Effect as Effect\nimport Tui.Screen\nimport Tui.Sub\n\ntype Msg\n    = Increment\n    | Quit\n\nkeyToMsg : Tui.Sub.KeyEvent -> Msg\nkeyToMsg event =\n    case event.key of\n        Tui.Sub.Character 'j' ->\n            Increment\n\n        _ ->\n            Quit\n\napp : Tui.ProgramConfig () Int Msg\napp =\n    { data = BackendTask.succeed ()\n    , init = \\() -> ( 0, Effect.none )\n    , update =\n        \\msg count ->\n            case msg of\n                Increment ->\n                    ( count + 1, Effect.none )\n\n                Quit ->\n                    ( count, Effect.exit )\n    , view = \\_ count -> Tui.Screen.text (\"Count: \" ++ String.fromInt count)\n    , subscriptions = \\_ -> Tui.Sub.onKeyPress keyToMsg\n    }\n\nrun : Script\nrun =\n    Tui.program app |> Tui.toScript\n\ntuiTests : TuiTest.Test\ntuiTests =\n    TuiTest.describe \"Counter\"\n        [ TuiTest.test \"increments with j\"\n            (TuiTest.start BackendTaskTest.init app)\n            [ TuiTest.pressKey 'j'\n            , TuiTest.ensureViewHas \"Count: 1\"\n            , TuiTest.expectRunning\n            ]\n        ]\n\nsuite : Test.Test\nsuite =\n    TuiTest.toTest tuiTests\n```\n\n@docs TuiTest, Test, Step\n\n\n## Starting a Test\n\nUse [`start`](#start) for the default terminal size, and\n[`startWithContext`](#startWithContext) when you want custom dimensions or\ncolor profile.\n\nBoth resolve `app.data` through [`Test.BackendTask`](Test-BackendTask).\n\n@docs start, startWithContext\n\n\n## Building Test Suites\n\nBuild a named test from a starting `TuiTest` and a list of [`Step`](#Step)\nvalues. Group tests with [`describe`](#describe).\n\n@docs test, describe, toTest, toNamedSnapshots\n\n\n## Other Runners\n\nUse these when you don't want to wrap a single test in [`test`](#test) -\nfor example, in plain `elm-test` `\\() -> ...` bodies or when feeding the\nvisual runner directly. [`runSteps`](#runSteps) is the lower-level\nprimitive that threads a list of steps through a `TuiTest` without\nfinalizing it.\n\n@docs expect, snapshots, runSteps, applyStep\n\n\n## Simulating Events\n\nSimulate user interactions in the order they would happen. Each function\nreturns a [`Step`](#Step); add them in order to the list passed to\n[`test`](#test) or [`expect`](#expect).\n\nPrefer the user-facing helpers first (`pressKey`, `clickText`, `paste`,\n`resize`).\n\n@docs pressKey, pressKeyWith, pressKeyN, paste, resize\n\n@docs click, clickText, scrollDown, scrollUp, scrollDownN, scrollUpN\n\n@docs advanceTime\n\n\n## Resolving Effects\n\nWhen your `update` returns a `Tui.Effect` that performs a `BackendTask`, the\ntest captures it as a pending effect instead of running it automatically.\nUse [`resolveEffect`](#resolveEffect) when the pending `BackendTask` can be\nresolved directly by `Test.BackendTask`.\n\nUse [`resolveEffectWith`](#resolveEffectWith) when you need to customize the\nsimulation, like stubbing an HTTP response:\n\n    TuiTest.resolveEffectWith\n        (BackendTaskTest.simulateCommand \"git\" \"M src/Main.elm\")\n\n@docs resolveEffect, resolveEffectWith\n\n\n## Screen Assertions\n\nAssert on the plain text content of the current screen. Failed assertions\nshow the full screen output for easy debugging.\n\nUse [`ensureViewHas`](#ensureViewHas) and\n[`ensureViewDoesNotHave`](#ensureViewDoesNotHave) for the common case. Use\n[`ensureView`](#ensureView) when you want a custom assertion, and\n[`ensureModel`](#ensureModel) when the important state is not visible on\nscreen.\n\n@docs ensureView, ensureViewHas, ensureViewDoesNotHave\n\n@docs ensureModel, annotateAssertion\n\n\n## Styled Text Assertions\n\nAssert on text that appears with specific styling (bold, color, etc.).\nAdjacent spans with the same style are merged before matching, so\nfragmented rendering like `<red>ERROR</red><red> message</red>` is\ntreated as a single `\"ERROR message\"` red region.\n\n@docs StyleCheck, bold, dim, italic, underline, fg, bg\n@docs ensureViewHasStyled, ensureViewDoesNotHaveStyled\n\n\n## Final Assertions\n\nEach scenario should end with a final-assertion `Step` that pins the\nexpected termination state. If pending effects remain unresolved, these\nchecks fail so you do not accidentally ignore `BackendTask`s.\n\n@docs expectRunning, expectExit, expectExitWith\n\n\n## Internal\n\nUsed by the generated `elm-pages test` stepper code.\n\n@docs BackendTaskSimulator\n\n\n## Snapshots\n\nEvery step records a snapshot automatically. `elm-pages test` reads those\nsnapshots and lets you step through them visually in the terminal.\n\nUse [`snapshots`](#snapshots) for low-level inspection, and\n[`withModelToString`](#withModelToString) when you also want to record model\nstate alongside each screen.\n\n@docs Snapshot, withModelToString\n\n","unions":[{"name":"Step","comment":" A single step in a TUI test pipeline - a key press, an effect\nresolution, an assertion, and so on. Steps are opaque values; build\nthem with the helpers in this module ([`pressKey`](#pressKey),\n[`ensureViewHas`](#ensureViewHas), [`resolveEffect`](#resolveEffect),\nand friends).\n\n    incrementSteps : List (TuiTest.Step Int Msg)\n    incrementSteps =\n        [ TuiTest.pressKey 'k'\n        , TuiTest.ensureViewHas \"Count: 1\"\n        , TuiTest.expectRunning\n        ]\n\n","args":["model","msg"],"cases":[]},{"name":"StyleCheck","comment":" A check on a single style attribute. Combine multiple checks in a list\nto require all of them. For example, `[ bold, fg Ansi.Color.red ]` means \"bold AND red.\"\n","args":[],"cases":[]},{"name":"Test","comment":" A named tree of TUI tests.\n\nUse [`test`](#test) for leaf tests and [`describe`](#describe) to group them,\nthen pass the resulting value to [`toTest`](#toTest) for `elm-test` or expose it\nfor `elm-pages test`.\n\n    import Test\n    import Test.Tui as TuiTest\n\n    tuiTests : TuiTest.Test\n    tuiTests =\n        TuiTest.describe \"Counter\"\n            [ TuiTest.test \"increments\" <|\n                counterScenario\n                    |> TuiTest.pressKey 'j'\n                    |> TuiTest.expectRunning\n            ]\n\n    suite : Test.Test\n    suite =\n        TuiTest.toTest tuiTests\n\n","args":[],"cases":[]},{"name":"TuiTest","comment":" An in-progress TUI test. Created with [`start`](#start) (or\n[`startWithContext`](#startWithContext)) and passed to [`test`](#test)\nalong with a list of [`Step`](#Step) values describing the user\ninteractions and assertions.\n","args":["model","msg"],"cases":[]}],"aliases":[{"name":"BackendTaskSimulator","comment":" Internal helper type used by [`resolveEffectWith`](#resolveEffectWith).\nMost tests do not need to refer to this directly; you can usually pass a\n`Test.BackendTask.simulate...` pipeline inline.\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n    import Test.Tui as TuiTest\n\n    simulateStars : TuiTest.BackendTaskSimulator msg -> TuiTest.BackendTaskSimulator msg\n    simulateStars =\n        BackendTaskTest.simulateHttpGet\n            \"https://api.github.com/repos/dillonkearns/elm-pages\"\n            (Encode.object [ ( \"stargazers_count\", Encode.int 1234 ) ])\n\n","args":["msg"],"type":"Test.BackendTask.Internal.BackendTaskTest msg"},{"name":"Snapshot","comment":" A snapshot of the TUI state at a point in the test pipeline. Used by\n[`toSnapshots`](#toSnapshots) for the interactive test stepper.\n\n`screen` is the `Tui.Screen.Screen` value (preserving styling), not a plain string.\nUse `Tui.Screen.toString` to get plain text, or render it through the TUI pipeline\nfor styled output.\n\n","args":[],"type":"{ label : String.String, screen : Tui.Screen.Screen, hasPendingEffects : Basics.Bool, modelState : Maybe.Maybe String.String, assertions : List.List String.String }"}],"values":[{"name":"advanceTime","comment":" Advance simulated time by the given number of milliseconds. Any\n[`Tui.Sub.everyMillis`](Tui-Sub#everyMillis) subscriptions fire for each\ninterval boundary crossed, passing the simulated `Time.Posix` at the moment\nof fire to the user's message constructor.\n\n    import Time\n\n    TuiTest.test \"animation advances one frame per 50ms tick\" <|\n        spinnerTest\n            |> TuiTest.advanceTime 50\n            |> TuiTest.ensureViewHas \"frame 1\"\n            |> TuiTest.advanceTime 50\n            |> TuiTest.ensureViewHas \"frame 2\"\n            |> TuiTest.expectRunning\n\nMulti-interval subscriptions fire independently at their own rates. If\nmultiple ticks fall in the same `advanceTime` call, they are delivered to\n`update` in chronological order; same-timestamp ticks from different\nintervals fire in subscription order. Catch-up semantics match the runtime:\neach interval fires at most once per `advanceTime` call with the actual\nsimulated fire time, not the target time.\n\nThe starting simulated clock is `1970-01-01T00:00:00Z` (posix 0). The first\nfire of `everyMillis n _` is at simulated posix `n`.\n\n","type":"Basics.Int -> Test.Tui.Step model msg"},{"name":"annotateAssertion","comment":" Add an assertion label to the most recent snapshot. The stepper shows\nthese beneath the action label, so custom helpers can describe what they\nchecked without creating a new snapshot.\n\nUse this when building companion helpers on top of [`ensureModel`](#ensureModel):\n\n    import Expect\n    import Test.Tui as TuiTest\n\n    ensureCount : Int -> List (TuiTest.Step Int msg)\n    ensureCount expected =\n        [ TuiTest.ensureModel (\\actual -> Expect.equal expected actual)\n        , TuiTest.annotateAssertion\n            (\"ensureCount \" ++ String.fromInt expected ++ \" ✓\")\n        ]\n\n","type":"String.String -> Test.Tui.Step model msg"},{"name":"applyStep","comment":" Apply a single [`Step`](#Step) to a `TuiTest`. Useful in meta\ntests that want to keep a chainable `|> step |> step` style with the\nnew opaque `Step` type. End-user tests should prefer [`test`](#test)\nor [`expect`](#expect) over chaining directly.\n\n    counterApp\n        |> TuiTest.applyStep (TuiTest.pressKey 'k')\n        |> TuiTest.applyStep TuiTest.expectRunning\n\n","type":"Test.Tui.Step model msg -> Test.Tui.TuiTest model msg -> Test.Tui.TuiTest model msg"},{"name":"bg","comment":" Match text with a specific background color.\n\n    |> TuiTest.ensureViewHasStyled [ TuiTest.bg Ansi.Color.blue ] \"Selected\"\n\n","type":"Ansi.Color.Color -> Test.Tui.StyleCheck"},{"name":"bold","comment":" Match bold text.\n\n    |> TuiTest.ensureViewHasStyled [ TuiTest.bold ] \"selected item\"\n\n","type":"Test.Tui.StyleCheck"},{"name":"click","comment":" Simulate a left mouse click at the given row and column (0-based).\n\n    [ TuiTest.click { row = 3, col = 5 } ]\n\n","type":"{ row : Basics.Int, col : Basics.Int } -> Test.Tui.Step model msg"},{"name":"clickText","comment":" Find a line containing the given text and simulate a click on it.\nLike elm-program-test's `clickButton`, finds elements by content instead of\ncoordinates, making tests resilient to layout changes.\n\n    [ TuiTest.clickText \"def5678\" ]\n\nFails with a helpful message if the text is not found on screen.\n\n","type":"String.String -> Test.Tui.Step model msg"},{"name":"describe","comment":" Group TUI tests under a shared heading.\n\n    import Test.Tui as TuiTest\n\n    counterTests : TuiTest.Test\n    counterTests =\n        TuiTest.describe \"Counter\"\n            [ TuiTest.test \"increments\"\n                counterScenario\n                [ TuiTest.pressKey 'j'\n                , TuiTest.expectRunning\n                ]\n            , TuiTest.test \"quits\"\n                counterScenario\n                [ TuiTest.pressKey 'q'\n                , TuiTest.expectExit\n                ]\n            ]\n\n","type":"String.String -> List.List Test.Tui.Test -> Test.Tui.Test"},{"name":"dim","comment":" Match dim text.\n","type":"Test.Tui.StyleCheck"},{"name":"ensureModel","comment":" Assert on the model directly. Useful for verifying internal state that\nisn't visible in the rendered output, or for building higher-level test\nhelpers that query opaque framework state (like `Layout.FrameworkModel`).\n\n    TuiTest.test \"counter is at 5\"\n        counterApp\n        [ TuiTest.pressKeyN 5 'j'\n        , TuiTest.ensureModel\n            (\\model -> Expect.equal 5 model.count)\n        , TuiTest.expectRunning\n        ]\n\n","type":"(model -> Expect.Expectation) -> Test.Tui.Step model msg"},{"name":"ensureView","comment":" Assert on the current screen content using a custom assertion function.\nThe function receives the plain text content (no styling) of the rendered\nscreen.\n\n    [ TuiTest.ensureView\n        (\\text ->\n            if String.contains \"Error\" text then\n                Expect.fail \"Should not show error\"\n\n            else\n                Expect.pass\n        )\n    ]\n\n","type":"(String.String -> Expect.Expectation) -> Test.Tui.Step model msg"},{"name":"ensureViewDoesNotHave","comment":" Assert that the current screen does NOT contain the given text.\n","type":"String.String -> Test.Tui.Step model msg"},{"name":"ensureViewDoesNotHaveStyled","comment":" Assert that the screen does NOT contain the given text with ALL of the\nspecified style checks.\n\n    TuiTest.test \"error text is not bold\"\n        myTest\n        [ TuiTest.ensureViewDoesNotHaveStyled [ TuiTest.bold ] \"Error\"\n        , TuiTest.expectRunning\n        ]\n\n","type":"List.List Test.Tui.StyleCheck -> String.String -> Test.Tui.Step model msg"},{"name":"ensureViewHas","comment":" Assert that the current screen contains the given text.\n\n    [ TuiTest.ensureViewHas \"Count: 0\" ]\n\n","type":"String.String -> Test.Tui.Step model msg"},{"name":"ensureViewHasStyled","comment":" Assert that the screen contains the given text rendered with ALL of the\nspecified style checks. Adjacent spans that satisfy the checks are merged\nbefore matching, so fragmented rendering is handled correctly.\n\n    TuiTest.test \"selected item is highlighted\"\n        myTest\n        [ TuiTest.ensureViewHasStyled [ TuiTest.bold, TuiTest.fg Ansi.Color.yellow ] \"selected\"\n        , TuiTest.expectRunning\n        ]\n\n","type":"List.List Test.Tui.StyleCheck -> String.String -> Test.Tui.Step model msg"},{"name":"expect","comment":" Run a list of [`Step`](#Step)s against a starting `TuiTest` and\nfinalize the result as an `Expect.Expectation`. Use this when writing\ntests in plain `elm-test` style:\n\n    import Expect\n    import Test exposing (Test, test)\n    import Test.Tui as TuiTest\n\n    suite : Test\n    suite =\n        test \"k increments\" <|\n            \\() ->\n                TuiTest.expect counterScenario\n                    [ TuiTest.pressKey 'k'\n                    , TuiTest.ensureViewHas \"Count: 1\"\n                    , TuiTest.expectRunning\n                    ]\n\n","type":"Test.Tui.TuiTest model msg -> List.List (Test.Tui.Step model msg) -> Expect.Expectation"},{"name":"expectExit","comment":" Assert that the TUI exited with code 0. Place this as the last step\nin your test list. Fails if there are unresolved pending `BackendTask`\neffects.\n","type":"Test.Tui.Step model msg"},{"name":"expectExitWith","comment":" Assert that the TUI exited with a specific exit code. Place this as\nthe last step in your test list. Fails if there are unresolved pending\n`BackendTask` effects.\n","type":"Basics.Int -> Test.Tui.Step model msg"},{"name":"expectRunning","comment":" Assert that the TUI is still running (has not exited). Place this as\nthe last step in your test list. Fails if there are unresolved pending\n`BackendTask` effects - use [`resolveEffect`](#resolveEffect) or\n[`resolveEffectWith`](#resolveEffectWith) earlier in the list to resolve\nthem.\n","type":"Test.Tui.Step model msg"},{"name":"fg","comment":" Match text with a specific foreground color.\n\n    |> TuiTest.ensureViewHasStyled [ TuiTest.fg Ansi.Color.red ] \"Error\"\n\n","type":"Ansi.Color.Color -> Test.Tui.StyleCheck"},{"name":"italic","comment":" Match italic text.\n","type":"Test.Tui.StyleCheck"},{"name":"paste","comment":" Simulate a bracketed paste event. Delivers the text as a single `OnPaste`\nevent, just like a real terminal with bracketed paste mode enabled. Use this\ninstead of typing character-by-character when testing paste behavior.\n\n    [ TuiTest.pressKey 'c' -- open commit dialog\n    , TuiTest.paste \"fix: null pointer\" -- paste commit message\n    , TuiTest.ensureViewHas \"fix: null pointer\"\n    ]\n\n","type":"String.String -> Test.Tui.Step model msg"},{"name":"pressKey","comment":" Simulate pressing a character key with no modifiers.\n\n    [ TuiTest.pressKey 'j' ]\n\n","type":"Char.Char -> Test.Tui.Step model msg"},{"name":"pressKeyN","comment":" Simulate pressing a character key N times.\n\n    -- Navigate down 7 items\n    [ TuiTest.pressKeyN 7 'j' ]\n\n","type":"Basics.Int -> Char.Char -> Test.Tui.Step model msg"},{"name":"pressKeyWith","comment":" Simulate pressing any key, including special keys and modifiers.\n\n    [ TuiTest.pressKeyWith\n        { key = Tui.Sub.Arrow Tui.Sub.Down\n        , modifiers = []\n        }\n    , TuiTest.pressKeyWith\n        { key = Tui.Sub.Character 's'\n        , modifiers = [ Tui.Sub.Ctrl ]\n        }\n    ]\n\n","type":"Tui.Sub.KeyEvent -> Test.Tui.Step model msg"},{"name":"resize","comment":" Simulate a terminal resize. The framework handles resize automatically:\nthis updates the `Context` that `view` receives and routes the new size through\nany `Tui.Sub.onResize` subscriptions.\n","type":"{ width : Basics.Int, height : Basics.Int } -> Test.Tui.Step model msg"},{"name":"resolveEffect","comment":" Resolve the next pending `BackendTask` effect with the default\n`Test.BackendTask` behavior.\n\nUse this for the common case where the pending effect can be resolved without\nextra setup, for example `BackendTask.succeed`, `map`, `andThen`, or other\npure `BackendTask` flows.\n\n    import BackendTask\n    import Test.BackendTask as BackendTaskTest\n    import Test.Tui as TuiTest\n    import Tui\n    import Tui.Effect as Effect\n    import Tui.Screen as Screen\n    import Tui.Sub\n\n    type Msg\n        = Fetch\n        | Fetched String\n\n    backendTaskTest : TuiTest.TuiTest String Msg\n    backendTaskTest =\n        TuiTest.start BackendTaskTest.init\n            { data = BackendTask.succeed \"idle\"\n            , init = \\status -> ( status, Effect.none )\n            , update =\n                \\msg status ->\n                    case msg of\n                        Fetch ->\n                            ( status\n                            , BackendTask.succeed \"done\"\n                                |> Effect.perform Fetched\n                            )\n\n                        Fetched newStatus ->\n                            ( newStatus, Effect.none )\n            , view = \\_ status -> Screen.text status\n            , subscriptions = \\_ -> Tui.Sub.onKeyPress (\\_ -> Fetch)\n            }\n\n    fetchTest : TuiTest.Test\n    fetchTest =\n        TuiTest.test \"fetches when f is pressed\"\n            backendTaskTest\n            [ TuiTest.pressKey 'f'\n            , TuiTest.resolveEffect\n            , TuiTest.ensureViewHas \"done\"\n            , TuiTest.expectRunning\n            ]\n\n","type":"Test.Tui.Step model msg"},{"name":"resolveEffectWith","comment":" Resolve the next pending `BackendTask` effect with a customized\n`Test.BackendTask` simulation pipeline.\n\nUse this when the pending effect needs extra simulation, like an HTTP\nresponse or shell command output.\n\nThe callback receives the same `Test.BackendTask` pipeline you would get from\n`BackendTaskTest.fromBackendTask`, so in practice you usually just pipe one or\nmore `BackendTaskTest.simulate...` helpers into it.\n\n    import Json.Encode as Encode\n    import Test.BackendTask as BackendTaskTest\n    import Test.Tui as TuiTest\n\n    [ TuiTest.pressKeyWith { key = Tui.Sub.Enter, modifiers = [] }\n    , TuiTest.resolveEffectWith\n        (BackendTaskTest.simulateHttpGet\n            \"https://api.github.com/repos/elm/core\"\n            (Encode.object [ ( \"stargazers_count\", Encode.int 7500 ) ])\n        )\n    , TuiTest.ensureViewHas \"Stars: 7500\"\n    ]\n\n","type":"(Test.Tui.BackendTaskSimulator msg -> Test.Tui.BackendTaskSimulator msg) -> Test.Tui.Step model msg"},{"name":"runSteps","comment":" Run a list of [`Step`](#Step)s against a `TuiTest`, returning the\nnew `TuiTest` (without finalizing). Use this to bake setup steps into\nthe result of a custom `start` helper, or to compose helper bundles\nreturned as `List Step`. End-of-test finalization is what\n[`test`](#test), [`expect`](#expect), and [`snapshots`](#snapshots) do\nfor you.\n\n    TuiTest.runSteps\n        [ TuiTest.pressKey 'k'\n        , TuiTest.ensureViewHas \"Count: 1\"\n        ]\n        counterTest\n\n","type":"List.List (Test.Tui.Step model msg) -> Test.Tui.TuiTest model msg -> Test.Tui.TuiTest model msg"},{"name":"scrollDown","comment":" Simulate a scroll-down event at the given position.\n","type":"{ row : Basics.Int, col : Basics.Int } -> Test.Tui.Step model msg"},{"name":"scrollDownN","comment":" Simulate N scroll-down events at the given position.\n\n    [ TuiTest.scrollDownN 10 { row = 3, col = 60 } ]\n\n","type":"Basics.Int -> { row : Basics.Int, col : Basics.Int } -> Test.Tui.Step model msg"},{"name":"scrollUp","comment":" Simulate a scroll-up event at the given position.\n","type":"{ row : Basics.Int, col : Basics.Int } -> Test.Tui.Step model msg"},{"name":"scrollUpN","comment":" Simulate N scroll-up events at the given position.\n\n    [ TuiTest.scrollUpN 5 { row = 3, col = 60 } ]\n\n","type":"Basics.Int -> { row : Basics.Int, col : Basics.Int } -> Test.Tui.Step model msg"},{"name":"snapshots","comment":" Run a list of [`Step`](#Step)s against a starting `TuiTest` and\nreturn the recorded snapshots. Use this when feeding the TUI stepper\nwithout going through [`test`](#test).\n","type":"Test.Tui.TuiTest model msg -> List.List (Test.Tui.Step model msg) -> List.List Test.Tui.Snapshot"},{"name":"start","comment":" Start a TUI test with a default 80×24 terminal and `TrueColor` profile.\nThis resolves `app.data` through [`Test.BackendTask`](Test-BackendTask)\nbefore the first snapshot.\n\nPure startup data usually uses `BackendTask.succeed`. For file, env, time, or\ndb-backed startup, seed the virtual environment through the `TestSetup`.\n\nIf your app subscribes to `Tui.Sub.onResize`, the initial context is fired\nautomatically.\n\n    import BackendTask\n    import Test.BackendTask as BackendTaskTest\n    import Test.Tui as TuiTest\n    import Tui\n    import Tui.Effect as Effect\n    import Tui.Screen as Screen\n    import Tui.Sub\n\n    type Msg\n        = Quit\n\n    counterTest : TuiTest.TuiTest Int Msg\n    counterTest =\n        TuiTest.start BackendTaskTest.init\n            { data = BackendTask.succeed 0\n            , init = \\count -> ( count, Effect.none )\n            , update = \\_ count -> ( count, Effect.exit )\n            , view = \\_ count -> Screen.text (\"Count: \" ++ String.fromInt count)\n            , subscriptions = \\_ -> Tui.Sub.onKeyPress (\\_ -> Quit)\n            }\n\nThe config record is the same shape you pass to [`Tui.program`](Tui#program),\nso you can share a single app value between your production script and your\ntests.\n\nUse [`startWithContext`](#startWithContext) for a custom terminal size.\n\n","type":"Test.BackendTask.TestSetup -> Tui.ProgramConfig data model msg -> Test.Tui.TuiTest model msg"},{"name":"startWithContext","comment":" Like [`start`](#start), but with a custom terminal context. Use this for\nresponsive layouts, small terminals, or color-profile-dependent rendering.\n\n    import BackendTask\n    import Test.BackendTask as BackendTaskTest\n    import Test.Tui as TuiTest\n    import Tui\n    import Tui.Effect as Effect\n    import Tui.Screen as Screen\n    import Tui.Sub\n\n    type Msg\n        = Resized { width : Int, height : Int }\n\n    resizedTest : TuiTest.TuiTest { width : Int, height : Int } Msg\n    resizedTest =\n        TuiTest.startWithContext\n            { width = 120, height = 40, colorProfile = Tui.TrueColor }\n            BackendTaskTest.init\n            { data = BackendTask.succeed { width = 0, height = 0 }\n            , init = \\model -> ( model, Effect.none )\n            , update =\n                \\msg _ ->\n                    case msg of\n                        Resized size ->\n                            ( size, Effect.none )\n            , view =\n                \\_ size ->\n                    Screen.text\n                        (String.fromInt size.width ++ \"x\" ++ String.fromInt size.height)\n            , subscriptions = \\_ -> Tui.Sub.onResize Resized\n            }\n\nIf your app subscribes to `Tui.Sub.onResize`, this initial context is also sent\nthrough that subscription once at startup.\n\n","type":"Tui.Context -> Test.BackendTask.TestSetup -> Tui.ProgramConfig data model msg -> Test.Tui.TuiTest model msg"},{"name":"test","comment":" Name a single test from a starting `TuiTest` and a list of\n[`Step`](#Step)s. The framework runs the steps and finalizes the test\nfor you.\n\n    import Test.Tui as TuiTest\n\n    counterTests : TuiTest.Test\n    counterTests =\n        TuiTest.test \"increments\"\n            counterScenario\n            [ TuiTest.pressKey 'j'\n            , TuiTest.expectRunning\n            ]\n\n","type":"String.String -> Test.Tui.TuiTest model msg -> List.List (Test.Tui.Step model msg) -> Test.Tui.Test"},{"name":"toNamedSnapshots","comment":" Flatten a named TUI test tree into named snapshot sequences.\n\nThis is used by the generated `elm-pages test` stepper code. The names include\nany enclosing [`describe`](#describe) labels so the selected test is easy to\nidentify.\n\n    import Test.Tui as TuiTest\n\n    snapshotNames : List String\n    snapshotNames =\n        tuiTests\n            |> TuiTest.toNamedSnapshots\n            |> List.map Tuple.first\n\n","type":"Test.Tui.Test -> List.List ( String.String, List.List Test.Tui.Snapshot )"},{"name":"toTest","comment":" Convert a named TUI test tree into an `elm-test` `Test.Test`.\n\n`elm-pages test` works with `TuiTest.Test` directly so it can show the\ninteractive stepper. Use `toTest` when you want to run the same named tests\nthrough plain `elm-test` without the `elm-pages` wrapper CLI.\n\n    import Test\n    import Test.Tui as TuiTest\n\n    suite : Test.Test\n    suite =\n        TuiTest.toTest tuiTests\n\n","type":"Test.Tui.Test -> Test.Test"},{"name":"underline","comment":" Match underlined text.\n","type":"Test.Tui.StyleCheck"},{"name":"withModelToString","comment":" Enable model state inspection in snapshots. Pass `Debug.toString` (or\nany `model -> String` function) and each snapshot will include the\npretty-printed model state.\n\nSince published packages cannot use `Debug.toString` directly, this must\nbe called from your test code:\n\n    TuiTest.snapshots counterTest\n        [ TuiTest.withModelToString Debug.toString\n        , TuiTest.pressKey 'k'\n        ]\n        |> List.map .modelState\n\n    -- [ Just \"{ count = 0 }\", Just \"{ count = 1 }\" ]\n\nFor nicer formatting, use `prettifyValue Debug.toString` from\n`dillonkearns/elm-snapshot` if you have it as a dependency.\n\n","type":"(model -> String.String) -> Test.Tui.Step model msg"}],"binops":[]},{"name":"Tui","comment":"\n\n\n## What It Is\n\nBuild a TUI (Text-Based User Interface) as an elm-pages script. An\n`elm-pages` CLI, defined by [`Pages.Script`](Pages-Script), lets you parse CLI\noptions and then execute a single [`BackendTask`](BackendTask) (no TEA\n`init`/`update`). For our purposes, we'll use the term TUI to mean interactive\n(like `vim`), and CLI to mean a more static command (like `grep` or `ls`).\n\n\n## What You Can Do\n\nA [`Tui.Program`](Tui#Program) lets you build\nan interactive Elm app that renders its view as text in the terminal\nand lets you `init` and `update` your `Model` in response to events:\n\n  - [Keypresses](Tui-Sub#Key)\n  - [Mouse Events](Tui-Sub#MouseEvent)\n  - [Paste events](Tui-Sub#onPaste)\n  - [Resize events](Tui-Sub#onResize)\n  - [Time passing](Tui-Sub#everyMillis)\n\nYou can also fire off a `BackendTask` and get back a `Msg`:\n\n  - [`perform`](Tui-Effect#perform)\n  - [`attempt`](Tui-Effect#attempt)\n\n\n## Example\n\nSimilar to in `elm-pages` Route Modules, the `data` function\nresolves a `BackendTask` prior to `init`\n\n    -- script/src/Counter.elm\n\n\n    module Counter exposing (run)\n\n    import Tui\n    import Tui.Screen as Screen\n    import Tui.Sub\n\n    run : Script\n    run =\n        Tui.program\n            { data = BackendTask.succeed ()\n            , init = \\() -> ( { count = 0 }, Effect.none )\n            , update = update\n            , view =\n                \\_ model ->\n                    Screen.text (\"Count: \" ++ String.fromInt model.count)\n            , subscriptions = \\_ -> Tui.Sub.onKeyPress KeyPressed\n            }\n            |> Tui.toScript\n\nYou run a TUI like you run any other [`Pages.Script.Script`](Pages-Script#Script), [see the elm-pages scripts docs page](https://elm-pages.com/docs/elm-pages-scripts)\n\n```sh\ncd script && npx elm-pages run src/Counter.elm\n```\n\nThe `data` field resolves before `init` runs, so you can cleanly initialize your `Model`\nwith data from reading files, fetching HTTP data, or running shell commands.\nResolving to a `FatalError` terminates the program with a non-zero exit code and prints\nan error message.\n\nFor a full interactive example that uses `data` to seed the initial repo,\n[`Tui.Input`](Tui-Input) to edit it, and `BackendTask.Http.getJson` to refetch\nstars, see [examples/end-to-end/script/src/TuiStars.elm](https://github.com/dillonkearns/elm-pages/blob/visual-test-runner/examples/end-to-end/script/src/TuiStars.elm).\nThat behavior is covered by [examples/end-to-end/script/tests/TuiStarsTests.elm](https://github.com/dillonkearns/elm-pages/blob/visual-test-runner/examples/end-to-end/script/tests/TuiStarsTests.elm).\n\n\n## Building a `Tui.Program`\n\n[`program`](#program) builds an opaque [`Program`](#Program) value; [`toScript`](#toScript)\nfinalizes it into a runnable `Pages.Script.Script`. Keeping the intermediate\n`Program` as its own type lets future releases add pipeable modifiers (themes,\nkey bindings, etc.) without changing the shape of any existing user code.\n\n@docs Program, ProgramConfig, program, toScript, programWithCliOptions\n\n\n## TUI or CLI\n\nFor programs that make sense both interactively and non-interactively (an\nagent piping output, a CI run), use [`programOrScript`](#programOrScript) to\nprovide a `script` branch alongside the `tui`. At runtime, `mode` decides\nwhich path to take. [`isInteractive`](#isInteractive) is the standard\nheuristic (isatty + CI + NO\\_COLOR) for common use.\n\n@docs Mode, programOrScript, isInteractive\n\n\n## Terminal Context\n\nPassed to your `view` function. Use `colorProfile` to adapt themes for\ndifferent terminal capabilities.\n\n@docs Context, ColorProfile\n\n","unions":[{"name":"ColorProfile","comment":" Terminal color capability, detected at init from environment variables.\nFollows charmbracelet/colorprofile's detection precedence:\n`$NO_COLOR` -> `$COLORTERM` -> known terminals -> `$TERM` suffix -> default.\n\nThe renderer automatically degrades colors based on the profile, so the Elm app\ncan always use the highest fidelity colors and they'll be converted. This\nfield lets apps adapt themes (e.g., use different palettes for 16-color).\n\n    view ctx model =\n        case ctx.colorProfile of\n            Tui.TrueColor ->\n                richColorView model\n\n            _ ->\n                basicColorView model\n\n","args":[],"cases":[["TrueColor",[]],["Color256",[]],["Color16",[]],["Mono",[]]]},{"name":"Mode","comment":" Which path should a [`programOrScript`](#programOrScript) take: the\ninteractive TUI, or the plain `BackendTask` fallback?\n\n    type Mode\n        = Tui\n        | Cli\n\nReturned from `mode : BackendTask FatalError Mode` in `programOrScript`.\nUse [`isInteractive`](#isInteractive) for the standard heuristic, or build\nyour own `BackendTask` to decide.\n\n","args":[],"cases":[["Tui",[]],["Cli",[]]]},{"name":"Program","comment":" An opaque TUI program value. Construct one with [`program`](#program) and\nfinalize it with [`toScript`](#toScript). Future releases will add pipeable\nmodifiers that work on this type without changing its public shape.\n","args":["data","model","msg"],"cases":[]}],"aliases":[{"name":"Context","comment":" Read-only terminal context provided to `view`.\n","args":[],"type":"{ width : Basics.Int, height : Basics.Int, colorProfile : Tui.ColorProfile }"},{"name":"ProgramConfig","comment":" The record shape that [`program`](#program) accepts, and also the shape\nthat [`Test.Tui.start`](Test-Tui#start) accepts so a single config value can\nbe shared between a production script and its tests.\n\nThe field list is intentionally locked for the v12 series: future TUI\ncapabilities will be added as pipeable modifiers on [`Program`](#Program)\nrather than as new required fields.\n\n","args":["data","model","msg"],"type":"{ data : BackendTask.BackendTask FatalError.FatalError data, init : data -> ( model, Tui.Effect.Effect msg ), update : msg -> model -> ( model, Tui.Effect.Effect msg ), view : Tui.Context -> model -> Tui.Screen.Screen, subscriptions : model -> Tui.Sub.Sub msg }"}],"values":[{"name":"isInteractive","comment":" The standard interactive-terminal heuristic: returns `Tui` if stdout and\nstdin are both TTYs and `CI`, `NO_COLOR`, and `TERM=dumb` are all unset.\nOtherwise returns `Cli`.\n\nUse this as the `mode` field of [`programOrScript`](#programOrScript). If\nyou need different rules (for example, always TUI regardless of pipes),\nsupply your own `BackendTask FatalError Mode` instead.\n\n    -- Default:\n    mode =\n        Tui.isInteractive\n\n    -- Force TUI:\n    mode =\n        BackendTask.succeed Tui.Tui\n\n    -- Opt out of TUI when a flag is set, otherwise use the default:\n    mode =\n        if cliOptions.plain then\n            BackendTask.succeed Tui.Cli\n\n        else\n            Tui.isInteractive\n\n","type":"BackendTask.BackendTask FatalError.FatalError Tui.Mode"},{"name":"program","comment":" Build an interactive TUI from a config record.\n\n    run : Script\n    run =\n        Tui.program\n            { data = BackendTask.succeed ()\n            , init = \\() -> ( { count = 0 }, Effect.none )\n            , update = update\n            , view = view\n            , subscriptions = subscriptions\n            }\n            |> Tui.toScript\n\nSee also [`programWithCliOptions`](#programWithCliOptions) and\n[`programOrScript`](#programOrScript).\n\n","type":"Tui.ProgramConfig data model msg -> Tui.Program data model msg"},{"name":"programOrScript","comment":" Run a TUI when the terminal is interactive, fall back to a non-interactive\nscript otherwise. The `mode` BackendTask decides which path to take. Pass\n[`isInteractive`](#isInteractive) for the standard isatty + CI + NO\\_COLOR\nheuristic, or your own `BackendTask FatalError Mode` for custom detection.\n\n    run : Script\n    run =\n        Tui.programOrScript\n            (Program.config |> Program.add ...)\n            (\\flags ->\n                { tui =\n                    { data = loadCommits\n                    , init = init\n                    , update = update\n                    , view = view\n                    , subscriptions = subscriptions\n                    }\n                , script =\n                    loadCommits\n                        |> BackendTask.andThen\n                            (\\commits -> Script.log (summarize commits))\n                , mode = Tui.isInteractive\n                }\n            )\n\nThe `tui`'s `data` only runs on the TUI path; the `script` path runs\nindependently so it can do its own loading or be a trivial no-op.\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> { tui : Tui.Program data model msg, script : BackendTask.BackendTask FatalError.FatalError (), mode : BackendTask.BackendTask FatalError.FatalError Tui.Mode }) -> Pages.Internal.Script.Script"},{"name":"programWithCliOptions","comment":" Run a TUI as a Script, with CLI option parsing.\n\n    run : Script\n    run =\n        Tui.programWithCliOptions\n            (Program.config\n                |> Program.add\n                    (OptionsParser.build identity\n                        |> OptionsParser.with\n                            (Option.optionalKeywordArg \"dir\"\n                                |> Option.withDefault \".\"\n                            )\n                    )\n            )\n            (\\dir ->\n                Tui.program\n                    { data = loadFiles dir\n                    , init = init\n                    , update = update\n                    , view = view\n                    , subscriptions = subscriptions\n                    }\n            )\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> Tui.Program data model msg) -> Pages.Internal.Script.Script"},{"name":"toScript","comment":" Turn a [`Program`](#Program) into a runnable `Pages.Script.Script`. This is\nthe terminal step of a TUI pipeline. Placing it at the end leaves room for\nfuture pipeable modifiers (e.g. `|> Tui.withTheme ...`) without forcing every\nuser to restructure.\n\n    run : Script\n    run =\n        Tui.program { ... } |> Tui.toScript\n\n","type":"Tui.Program data model msg -> Pages.Internal.Script.Script"}],"binops":[]},{"name":"Tui.Attribute","comment":" Text decoration attributes for styled terminal output. Used with\n[`Tui.Screen.withAttributes`](Tui-Screen#withAttributes) to apply a dynamic\nset of attributes to a `Screen`, and with\n[`Tui.Screen.Advanced.styleAttributes`](Tui-Screen-Advanced#styleAttributes)\nto inspect the attribute list on an existing `Style`.\n\n    import Tui.Attribute as Attr\n    import Tui.Screen as Screen\n\n    Screen.text \"Heading\"\n        |> Screen.withAttributes [ Attr.Bold, Attr.Underline ]\n\nFor the common case of applying a single attribute to a `Screen`, prefer the\ndedicated builders ([`Tui.Screen.bold`](Tui-Screen#bold),\n[`Tui.Screen.italic`](Tui-Screen#italic), etc.) directly.\n\n@docs Attribute\n\n","unions":[{"name":"Attribute","comment":" A text decoration attribute.\n","args":[],"cases":[["Bold",[]],["Dim",[]],["Italic",[]],["Underline",[]],["Strikethrough",[]],["Inverse",[]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Tui.Effect","comment":" The core Effects for [`Tui.Program`](Tui#Program)s.\nMost notably, your TUI can resolve a [`BackendTask`](BackendTask)\nwithin its `init` and `update` and get back the result\nas a `Msg`.\n\n    update : Msg -> Model -> ( Model, Effect Msg )\n    update msg model =\n        case msg of\n            PressedStage file ->\n                ( { model | staging = Just file }\n                , Script.exec \"git\" [ \"add\", file ]\n                    |> Effect.perform (\\() -> StagingComplete file)\n                )\n\n            StagingComplete file ->\n                ( { model | staging = Nothing }\n                , Effect.none\n                )\n\n@docs Effect\n\n\n## Performing `BackendTask`s\n\n@docs perform, attempt\n\n\n## Combining and Transforming\n\n@docs none, batch, map\n\n\n## Exiting Program\n\n@docs exit, exitWithCode\n\n","unions":[],"aliases":[{"name":"Effect","comment":" An `Effect` can be passed to `update` or `init`. Like the `Cmd` part of a standard\nElm TEA application.\n","args":["msg"],"type":"Tui.Effect.Internal.Effect msg"}],"values":[{"name":"attempt","comment":" Like `perform`, but surfaces errors as `Result` values for you to handle `BackendTask` errors gracefully.\n\n    Script.command \"git\" [ \"diff\", file ]\n        |> BackendTask.mapError (\\_ -> \"git diff failed\")\n        |> Effect.attempt\n            (\\result ->\n                case result of\n                    Ok text ->\n                        GotDiff text\n\n                    Err reason ->\n                        DiffFailed reason\n            )\n\nUseful in combination with [`BackendTask.mapError`](BackendTask#mapError).\n\n","type":"(Result.Result error a -> msg) -> BackendTask.BackendTask error a -> Tui.Effect.Effect msg"},{"name":"batch","comment":" Combine multiple effects sequentially.\n","type":"List.List (Tui.Effect.Effect msg) -> Tui.Effect.Effect msg"},{"name":"exit","comment":" Exit the TUI (exit code 0). Terminal state is restored.\n","type":"Tui.Effect.Effect msg"},{"name":"exitWithCode","comment":" Exit the TUI with a specific exit code. Terminal state is restored.\n","type":"Basics.Int -> Tui.Effect.Effect msg"},{"name":"map","comment":" Transform the message type of an effect.\n","type":"(a -> b) -> Tui.Effect.Effect a -> Tui.Effect.Effect b"},{"name":"none","comment":" No effect.\n","type":"Tui.Effect.Effect msg"},{"name":"perform","comment":" Run a `BackendTask` and get the resolved data back in a `Msg`. If the\n`BackendTask` fails, the `FatalError` propogates and the TUI exits and prints\nthe `FatalError`'s message.\n\n    Script.command \"git\" [ \"status\", \"--porcelain\" ]\n        |> BackendTask.map parseFiles\n        |> Effect.perform GotFiles\n\n","type":"(a -> msg) -> BackendTask.BackendTask FatalError.FatalError a -> Tui.Effect.Effect msg"}],"binops":[]},{"name":"Tui.Input","comment":" Text input primitive for TUI applications.\n\nHandles character insertion, deletion, cursor movement, and renders\nwith an inverse-video cursor indicator (the standard TUI convention).\n\n    import Tui.Input as Input\n\n    type alias Model =\n        { input : Input.State }\n\n    -- In update:\n    case event.key of\n        Tui.Sub.Escape -> dismissModal\n        Tui.Sub.Enter -> submit (Input.text model.input)\n        _ -> { model | input = Input.update event model.input }\n\n    -- In view:\n    Input.view { width = 40 } model.input\n\n@docs State, init, update, insertText, view, viewMasked, text\n\n","unions":[{"name":"State","comment":" Opaque state for a text input. Tracks content and cursor position.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"init","comment":" Create an input with initial text. Cursor starts at the end.\n","type":"String.String -> Tui.Input.State"},{"name":"insertText","comment":" Insert a string at the cursor position. Useful for handling paste events:\nwhen bracketed paste delivers a chunk of text, insert it all at once.\n\n    case msg of\n        GotPaste pastedText ->\n            { model | input = Input.insertText pastedText model.input }\n\n","type":"String.String -> Tui.Input.State -> Tui.Input.State"},{"name":"text","comment":" Extract the current text value.\n","type":"Tui.Input.State -> String.String"},{"name":"update","comment":" Handle a key event. Returns updated state.\n\nHandles: character insertion, Backspace, Delete, Arrow Left/Right,\nHome, End, Ctrl+A (home), Ctrl+E (end), Ctrl+K (kill to end),\nCtrl+U (kill to start).\n\nKeys the input doesn't handle (Escape, Enter, Tab, etc.) return\nthe state unchanged. Match those keys before calling this:\n\n    case event.key of\n        Tui.Sub.Escape ->\n            ( closeInput model, Effect.none )\n\n        Tui.Sub.Enter ->\n            ( submit model, Effect.none )\n\n        _ ->\n            ( { model | input = Input.update event model.input }, Effect.none )\n\n","type":"Tui.Sub.KeyEvent -> Tui.Input.State -> Tui.Input.State"},{"name":"view","comment":" Render the input as a Screen with an inverse-video cursor.\n\nThe `width` constrains how wide the input renders.\n\n","type":"{ width : Basics.Int } -> Tui.Input.State -> Tui.Screen.Screen"},{"name":"viewMasked","comment":" Render the input as a Screen with masked characters and an inverse-video\ncursor.\n\nUseful for password-style prompts where you still want users to see the cursor\nposition while hiding the actual text.\n\n","type":"{ width : Basics.Int } -> Tui.Input.State -> Tui.Screen.Screen"}],"binops":[]},{"name":"Tui.Screen","comment":" Styled terminal output for a [`Tui.Program`](Tui#Program)'s `view` function.\n\n    import Tui.Screen as Screen\n\n    view : Tui.Context -> Model -> Screen.Screen\n    view _ model =\n        Screen.lines\n            [ Screen.text \"Hello, TUI!\" |> Screen.bold\n            , Screen.blank\n            , Screen.text (\"Count: \" ++ String.fromInt model.count)\n                |> Screen.fg Ansi.Color.cyan\n            ]\n\n\n## Building Screens\n\nScreens compose vertically with [`lines`](#lines) and horizontally with\n[`concat`](#concat). For split-pane layouts, see the `Tui.Layout` module in\nthe `tui-widgets` package.\n\n@docs Screen, text, lines, concat, empty, blank\n\n\n## Styling\n\nPipeline-style builders that compose on any `Screen` (text, concat, lines):\n\n    Screen.text \"warning\" |> Screen.fg Ansi.Color.yellow |> Screen.bold\n\n    Screen.concat [ a, b ] |> Screen.dim -- dims both a and b\n\nFor reusable styling, define a styling function and apply it to multiple\nscreens:\n\n    borderStyling : Screen -> Screen\n    borderStyling = Screen.fg Ansi.Color.gray >> Screen.bold\n\n    Screen.text \"|\" |> borderStyling\n\n@docs fg, bg, bold, dim, italic, underline, strikethrough, inverse, withAttributes, link\n\nSee [`Tui.Attribute`](Tui-Attribute) for the `Attribute` type used by\n[`withAttributes`](#withAttributes).\n\n@docs Style\n\n`Style` is opaque so that future releases can add fields without breaking\nuser code. To inspect a `Style` obtained from a\n[`Tui.Screen.Advanced.Span`](Tui-Screen-Advanced#Span), use the getter\nfunctions in [`Tui.Screen.Advanced`](Tui-Screen-Advanced).\n\n\n## Text Manipulation\n\n@docs truncateWidth, wrapWidth\n\n\n## Inspecting\n\n@docs toString\n\nFor framework-level consumers that need to inspect rendered lines or rebuild\nstyled output, see [`Tui.Screen.Advanced`](Tui-Screen-Advanced).\n\n","unions":[],"aliases":[{"name":"Screen","comment":" Opaque type representing terminal output. Built from primitives, rendered by\nthe framework.\n","args":[],"type":"Tui.Screen.Internal.Screen Tui.Screen.Style"},{"name":"Style","comment":" Terminal cell style: foreground color, background color, text\nattributes, and optional hyperlink.\n\nOpaque so the field list can grow in future releases without breaking user\ncode. To build styled text, use the Screen-level builders\n([`fg`](#fg), [`bold`](#bold), etc.) directly on a `Screen`. To read the\nstyle of a [`Tui.Screen.Advanced.Span`](Tui-Screen-Advanced#Span), use\n[`Tui.Screen.Advanced.styleForeground`](Tui-Screen-Advanced#styleForeground)\nand friends.\n\n","args":[],"type":"Tui.Screen.Internal.Style"}],"values":[{"name":"bg","comment":" Set background color on a Screen.\n\n    Screen.text \"selected\" |> Screen.bg Ansi.Color.blue\n\n","type":"Ansi.Color.Color -> Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"blank","comment":" A blank line that renders one empty line. Useful as a vertical spacer\nin [`lines`](#lines):\n\n    Screen.lines\n        [ Screen.text \"Title\"\n        , Screen.blank\n        , Screen.text \"Content\"\n        ]\n\nNote: this is different from [`empty`](#empty) which renders nothing.\n`blank` produces a visible gap (one empty row).\n\n","type":"Tui.Screen.Screen"},{"name":"bold","comment":" Apply bold attribute.\n\n    Screen.text \"important\" |> Screen.bold\n\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"concat","comment":" Concatenate screens horizontally, row by row. If one child has more lines\nthan another, the extra trailing lines are preserved.\n","type":"List.List Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"dim","comment":" Apply dim attribute.\n\n    Screen.text \"muted\" |> Screen.dim\n\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"empty","comment":" Empty screen that renders nothing and takes up zero lines. Use this as\na \"null\" value, for example with `Maybe.withDefault`:\n\n    case maybeError of\n        Just err ->\n            Screen.text err |> Screen.fg Ansi.Color.red\n\n        Nothing ->\n            Screen.empty\n\nNote: this is different from [`blank`](#blank) which renders one empty line.\n`empty` produces no output at all.\n\n","type":"Tui.Screen.Screen"},{"name":"fg","comment":" Set foreground color on a Screen. Composes with pipeline syntax:\n\n    Screen.text \"error\" |> Screen.fg Ansi.Color.red\n\n    Screen.text \"warning\" |> Screen.fg Ansi.Color.yellow |> Screen.bold\n\n","type":"Ansi.Color.Color -> Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"inverse","comment":" Apply inverse (reverse video) attribute.\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"italic","comment":" Apply italic attribute.\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"lines","comment":" Stack screens vertically. Each item starts on a new line.\n","type":"List.List Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"link","comment":" Wrap a Screen in a clickable hyperlink (OSC 8). In terminals that support it,\nthe text becomes a clickable link. In unsupported terminals, the escape sequence\nis silently ignored and the text renders normally.\n\n    Screen.text \"elm/core\"\n        |> Screen.underline\n        |> Screen.fg Ansi.Color.blue\n        |> Screen.link { url = \"https://package.elm-lang.org/packages/elm/core/latest\" }\n\n","type":"{ url : String.String } -> Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"strikethrough","comment":" Apply strikethrough attribute.\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"text","comment":" Unstyled text.\n","type":"String.String -> Tui.Screen.Screen"},{"name":"toString","comment":" Convert a Screen to a plain text string (no ANSI codes). Useful for testing,\nlayout measurement, and debugging.\n","type":"Tui.Screen.Screen -> String.String"},{"name":"truncateWidth","comment":" Truncate a Screen to a maximum width in columns, preserving styles.\nAdds \"\\\\u{2026}\" if truncated. Works on the first line only. Returns `empty`\nfor non-positive widths.\n","type":"Basics.Int -> Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"underline","comment":" Apply underline attribute.\n","type":"Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"withAttributes","comment":" Apply a list of attributes at once. Useful when the attribute set is\ndynamic (from a config, theme, or computed list). Equivalent to chaining the\nindividual builders.\n\n    import Tui.Attribute as Attr\n    import Tui.Screen as Screen\n\n    Screen.text \"Heading\"\n        |> Screen.withAttributes [ Attr.Bold, Attr.Underline ]\n\n","type":"List.List Tui.Attribute.Attribute -> Tui.Screen.Screen -> Tui.Screen.Screen"},{"name":"wrapWidth","comment":" Wrap a Screen to a maximum width, preserving styles across line breaks.\nReturns a list of Screens, one per wrapped line.\n\n    Screen.concat\n        [ Screen.text \"This is a \"\n        , Screen.text \"very important\" |> Screen.bold\n        , Screen.text \" paragraph about decoding JSON values.\"\n        ]\n        |> Screen.wrapWidth 30\n    -- Returns 3 Screens with \"very important\" still bold\n\nWraps at word boundaries (spaces). Words longer than `maxWidth` are broken\nmid-word. Existing line breaks are preserved. Returns `[]` for empty screens\nor non-positive widths.\n\n","type":"Basics.Int -> Tui.Screen.Screen -> List.List Tui.Screen.Screen"}],"binops":[]},{"name":"Tui.Screen.Advanced","comment":" Framework-level helpers for inspecting and rebuilding styled terminal lines.\n\nMost app code should stay in [`Tui.Screen`](Tui-Screen). Reach for this module\nwhen you need to preserve styling while transforming rendered text. Typical use\ncases are highlighting search matches in an already-styled buffer, applying\nsyntax coloring to rendered output, splitting lines at arbitrary character\npositions without losing styles, or implementing diff-style overlays. The\n`tui-widgets` package uses this internally for its search and layout features.\n\n    import Tui.Screen as Screen\n    import Tui.Screen.Advanced as Advanced\n\n    screen : Screen.Screen\n    screen =\n        Screen.concat\n            [ Screen.text \"hello \"\n            , Screen.text \"world\" |> Screen.fg Ansi.Color.green\n            ]\n\n    screen\n        |> Advanced.toLines\n        |> List.head\n        |> Maybe.withDefault []\n        |> Advanced.fromLine\n        |> Screen.toString\n\n\n## Lines\n\n@docs Line, Span, toLines\n\n\n## Rebuilding\n\n@docs fromLine\n\n\n## Reading a Style\n\nGetters for the `style` field of a [`Span`](#Span). The `Style` type from\n[`Tui.Screen`](Tui-Screen) is opaque so that future releases can add\nfields without breaking user code; these getters are the supported way\nto inspect it.\n\n@docs styleForeground, styleBackground, styleAttributes, styleHyperlink\n\n\n## Flat Style Records\n\nFor lower-level access, `FlatStyle` is a plain record with one `Bool`\nfield per [`Tui.Attribute.Attribute`](Tui-Attribute#Attribute) plus the\ncolor/hyperlink fields. Conversion to/from a `Style` is provided for\nframework-level consumers that need to manipulate styles as records.\n\n@docs FlatStyle, styleToFlatStyle, flatStyleToStyle\n\n","unions":[],"aliases":[{"name":"FlatStyle","comment":" Flat record representation of a [`Style`](Tui-Screen#Style), with one\n`Bool` per attribute plus color and hyperlink fields.\n","args":[],"type":"Tui.Screen.Internal.FlatStyle"},{"name":"Line","comment":" A rendered screen line represented as styled spans.\n\n    import Tui.Screen.Advanced as Advanced\n\n    line : Advanced.Line\n    line =\n        []\n\n","args":[],"type":"List.List Tui.Screen.Advanced.Span"},{"name":"Span","comment":" A styled segment within a rendered line. Obtain spans by calling\n[`toLines`](#toLines) on a rendered `Tui.Screen.Screen`. Inspect the `style`\nfield with [`styleForeground`](#styleForeground),\n[`styleBackground`](#styleBackground),\n[`styleAttributes`](#styleAttributes), and\n[`styleHyperlink`](#styleHyperlink).\n","args":[],"type":"{ text : String.String, style : Tui.Screen.Style }"}],"values":[{"name":"flatStyleToStyle","comment":" Convert a flat record back to an opaque `Style`. Inverse of\n[`styleToFlatStyle`](#styleToFlatStyle).\n","type":"Tui.Screen.Advanced.FlatStyle -> Tui.Screen.Style"},{"name":"fromLine","comment":" Rebuild a single rendered line from styled spans.\n\nAn empty line becomes [`Tui.Screen.blank`](Tui-Screen#blank), preserving the\nfact that it takes up one terminal row.\n\nTypical use is a round-trip: obtain spans with [`toLines`](#toLines), transform\nthem (e.g. to highlight search matches), and reassemble with `fromLine`.\n\n    import Tui.Screen as Screen\n    import Tui.Screen.Advanced as Advanced\n\n    source : Screen.Screen\n    source =\n        Screen.text \"hello\" |> Screen.bold\n\n    roundTripped : Screen.Screen\n    roundTripped =\n        source\n            |> Advanced.toLines\n            |> List.head\n            |> Maybe.withDefault []\n            |> Advanced.fromLine\n\n","type":"Tui.Screen.Advanced.Line -> Tui.Screen.Screen"},{"name":"styleAttributes","comment":" Read the attribute list of a [`Style`](Tui-Screen#Style).\n\n    import Tui.Attribute as Attr\n    import Tui.Screen.Advanced as Advanced\n\n    if List.member Attr.Bold (Advanced.styleAttributes span.style) then\n        ...\n\n","type":"Tui.Screen.Style -> List.List Tui.Attribute.Attribute"},{"name":"styleBackground","comment":" Read the background color of a [`Style`](Tui-Screen#Style).\n","type":"Tui.Screen.Style -> Maybe.Maybe Ansi.Color.Color"},{"name":"styleForeground","comment":" Read the foreground color of a [`Style`](Tui-Screen#Style).\n","type":"Tui.Screen.Style -> Maybe.Maybe Ansi.Color.Color"},{"name":"styleHyperlink","comment":" Read the hyperlink URL of a [`Style`](Tui-Screen#Style), if any.\n","type":"Tui.Screen.Style -> Maybe.Maybe String.String"},{"name":"styleToFlatStyle","comment":" Convert an opaque `Style` to its flat record form.\n","type":"Tui.Screen.Style -> Tui.Screen.Advanced.FlatStyle"},{"name":"toLines","comment":" Flatten a `Tui.Screen.Screen` into rendered lines of styled spans.\n\n    import Ansi.Color\n    import Tui.Screen as Screen\n    import Tui.Screen.Advanced as Advanced\n\n    lines : List Advanced.Line\n    lines =\n        Screen.concat\n            [ Screen.text \"hello \"\n            , Screen.text \"world\" |> Screen.fg Ansi.Color.green\n            ]\n            |> Advanced.toLines\n\n","type":"Tui.Screen.Screen -> List.List Tui.Screen.Advanced.Line"}],"binops":[]},{"name":"Tui.Sub","comment":" Subscriptions for a [`Tui.Program`](Tui#Program).\n\n    import Tui.Sub\n\n    type Msg\n        = KeyPressed Tui.Sub.KeyEvent\n        | Resized { width : Int, height : Int }\n\n    subscriptions : Model -> Tui.Sub.Sub Msg\n    subscriptions _ =\n        Tui.Sub.batch\n            [ Tui.Sub.onKeyPress KeyPressed\n            , Tui.Sub.onResize Resized\n            ]\n\n    update : Msg -> Model -> ( Model, Effect.Effect Msg )\n    update msg model =\n        case msg of\n            KeyPressed event ->\n                case event.key of\n                    Tui.Sub.Character 'q' ->\n                        ( model, Effect.exit )\n\n                    Tui.Sub.Arrow Tui.Sub.Up ->\n                        ( { model | count = model.count + 1 }\n                        , Effect.none\n                        )\n\n                    _ ->\n                        ( model, Effect.none )\n\n            Resized _ ->\n                ( model, Effect.none )\n\n@docs Sub\n\n\n## Subscribing\n\n@docs none, batch, onKeyPress, onMouse, onPaste, onResize, everyMillis\n\n\n## Transforming\n\n@docs map\n\n\n## Keyboard events\n\nThe values you pattern-match on inside `update` when handling\n[`onKeyPress`](#onKeyPress).\n\n@docs KeyEvent, Key, Direction, Modifier\n\n\n## Mouse events\n\nThe values you pattern-match on inside `update` when handling\n[`onMouse`](#onMouse).\n\n@docs MouseEvent, MouseButton\n\n","unions":[{"name":"Direction","comment":" Arrow key direction.\n","args":[],"cases":[["Up",[]],["Down",[]],["Left",[]],["Right",[]]]},{"name":"Key","comment":" Key values.\n","args":[],"cases":[["Character",["Char.Char"]],["Enter",[]],["Escape",[]],["Tab",[]],["Backspace",[]],["Delete",[]],["Arrow",["Tui.Sub.Direction"]],["FunctionKey",["Basics.Int"]],["Home",[]],["End",[]],["PageUp",[]],["PageDown",[]]]},{"name":"Modifier","comment":" Key modifier.\n","args":[],"cases":[["Ctrl",[]],["Alt",[]],["Shift",[]]]},{"name":"MouseButton","comment":" Mouse button for click events.\n","args":[],"cases":[["LeftButton",[]],["MiddleButton",[]],["RightButton",[]]]},{"name":"MouseEvent","comment":" Mouse event from the terminal. Uses SGR extended mouse mode for accurate\ncoordinates on any terminal size.\n\nCoordinates are 0-based: `{ row = 0, col = 0 }` is the top-left corner.\n\n`amount` on scroll events is the number of coalesced scroll steps. Rapid\nscrolling batches events on the JS side (like gocui's event drain) so you\nget one event with `amount = 5` instead of 5 separate events. Multiply your\nscroll distance by `amount` for responsive feel.\n\n","args":[],"cases":[["Click",["{ row : Basics.Int, col : Basics.Int, button : Tui.Sub.MouseButton }"]],["ScrollUp",["{ row : Basics.Int, col : Basics.Int, amount : Basics.Int }"]],["ScrollDown",["{ row : Basics.Int, col : Basics.Int, amount : Basics.Int }"]]]},{"name":"Sub","comment":" A TUI subscription. Build values with [`none`](#none), [`batch`](#batch),\n[`onKeyPress`](#onKeyPress), [`onMouse`](#onMouse), [`onPaste`](#onPaste),\n[`onResize`](#onResize), and [`everyMillis`](#everyMillis). The constructors\nare implementation details; application code should not pattern-match on them.\n","args":["msg"],"cases":[["SubNone",[]],["SubBatch",["List.List (Tui.Sub.Sub msg)"]],["OnKeyPress",["Tui.Sub.KeyEvent -> msg"]],["OnMouse",["Tui.Sub.MouseEvent -> msg"]],["OnPaste",["String.String -> msg"]],["OnResize",["{ width : Basics.Int, height : Basics.Int } -> msg"]],["Every",["Basics.Int","Time.Posix -> msg"]]]}],"aliases":[{"name":"KeyEvent","comment":" A keyboard event from the terminal.\n","args":[],"type":"{ key : Tui.Sub.Key, modifiers : List.List Tui.Sub.Modifier }"}],"values":[{"name":"batch","comment":" Combine multiple subscriptions.\n","type":"List.List (Tui.Sub.Sub msg) -> Tui.Sub.Sub msg"},{"name":"everyMillis","comment":" Periodic tick at the given interval in milliseconds. The message\nconstructor receives the wall-clock time ([`Time.Posix`](https://package.elm-lang.org/packages/elm/time/latest/Time#Posix)) at which the tick\nactually fired. You can store it in your `Model` and subtract from the previous tick\nto compute smooth deltas for animations.\n\n    import Time\n\n    type Msg\n        = SpinnerTick Time.Posix\n        | ClockTick Time.Posix\n\n    subscriptions _ =\n        Tui.Sub.batch\n            [ Tui.Sub.everyMillis 50 SpinnerTick\n            , Tui.Sub.everyMillis 1000 ClockTick\n            ]\n\nEach unique interval runs independently. When the runtime is blocked\nfor longer than the interval, catch-up fires once with the actual elapsed\n`Posix` (it does not rapid-fire the missed ticks).\n\n","type":"Basics.Int -> (Time.Posix -> msg) -> Tui.Sub.Sub msg"},{"name":"map","comment":" Transform the message type of a subscription.\n","type":"(a -> b) -> Tui.Sub.Sub a -> Tui.Sub.Sub b"},{"name":"none","comment":" No subscriptions.\n","type":"Tui.Sub.Sub msg"},{"name":"onKeyPress","comment":" Subscribe to [keyboard events](#keyboard-events).\n","type":"(Tui.Sub.KeyEvent -> msg) -> Tui.Sub.Sub msg"},{"name":"onMouse","comment":" Subscribe to mouse events (click, scroll). Enables SGR extended mouse\nreporting in the terminal when subscribed.\n","type":"(Tui.Sub.MouseEvent -> msg) -> Tui.Sub.Sub msg"},{"name":"onPaste","comment":" Subscribe to paste events. You can receive paste events and\nchoose how to handle it in your program (treating it as normal text,\nor as you sometimes see in TUIs, bracketed text).\n","type":"(String.String -> msg) -> Tui.Sub.Sub msg"},{"name":"onResize","comment":" Subscribe to terminal resize events. Fires once on init with the starting\nterminal dimensions, and again whenever the terminal is resized. The record\ncontains `width` and `height` in columns/rows.\n\nUse this to store dimensions in your model (e.g., for `Layout.withContext`\nor `Layout.handleMouse`).\n\n    Tui.Sub.onResize (\\{ width, height } -> Resized width height)\n\n","type":"({ width : Basics.Int, height : Basics.Int } -> msg) -> Tui.Sub.Sub msg"}],"binops":[]},{"name":"UrlPath","comment":" Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).\n\nThis helper lets you combine together path parts without worrying about having too many or too few slashes.\nThese two examples will result in the same URL, even though the first example has trailing and leading slashes, and the\nsecond does not.\n\n    UrlPath.join [ \"/blog/\", \"/post-1/\" ]\n        |> UrlPath.toAbsolute\n    --> \"/blog/post-1\"\n\n    UrlPath.join [ \"blog\", \"post-1\" ]\n        |> UrlPath.toAbsolute\n    --> \"/blog/post-1\"\n\nWe can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:\n\n    UrlPath.join [ \"/articles/archive/\", \"1977\", \"06\", \"10\", \"post-1\" ]\n        |> UrlPath.toAbsolute\n    --> \"/articles/archive/1977/06/10/post-1\"\n\n\n## Creating UrlPaths\n\n@docs UrlPath, join, fromString\n\n\n## Turning UrlPaths to String\n\n@docs toAbsolute, toRelative, toSegments\n\n","unions":[],"aliases":[{"name":"UrlPath","comment":" The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).\n","args":[],"type":"List.List String.String"}],"values":[{"name":"fromString","comment":" Create a UrlPath from a path String.\n\n    UrlPath.fromString \"blog/post-1/\"\n        |> UrlPath.toAbsolute\n        |> Expect.equal \"/blog/post-1\"\n\n","type":"String.String -> UrlPath.UrlPath"},{"name":"join","comment":" Turn a Path to a relative URL.\n","type":"UrlPath.UrlPath -> UrlPath.UrlPath"},{"name":"toAbsolute","comment":" Turn a UrlPath to an absolute URL (with no trailing slash).\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toRelative","comment":" Turn a UrlPath to a relative URL.\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toSegments","comment":" ","type":"String.String -> List.List String.String"}],"binops":[]}]