From e4f119e00805bd23d7b35d3218081e887c186a24 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:48:02 +0100 Subject: [PATCH 001/165] docs: design the brief session's relay, page and route back to GitHub Fills spc-2610141709430146 with the design and its four steps, and captures the privacy-hygiene false positive on a dotted RFC section number found while checking the record. Assisted-by: Claude:claude-opus-5-5 --- ...6-the-product-thinker-and-the-technical.md | 78 ++++++++++++++++++- ...bcd-lint-s-privacy-hygiene-rule-reads-a.md | 16 ++++ 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 .abcd/work/issues/open/iss-2610141947553722-abcd-lint-s-privacy-hygiene-rule-reads-a.md diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index a86a8b51da..6be5148351 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -9,13 +9,83 @@ production_mode: hand-written ## Summary -_Draft: describe what spc-2610141709430146 delivers for itd-2610091016262335 — scope, approach, and how it satisfies the intent's Acceptance Criteria. This spec is the design record the fidelity review audits against._ +A team opens its brief at abcdesign.app///brief/ during a call and edits it together live. The page is static. The session runs on a relay the team owns, and changes go back to GitHub as issues or one pull request, never through anything abcd runs. + +**One relay, two routes.** `abcd session serve` is a verb in the abcd binary. It listens on loopback by default, and it serves three things: +- the session's WebSocket at `/session` +- the sign-in exchange at `/auth/token` +- a health check at `/healthz` + +The relay reaches the team by one of two routes: +- **The host's own machine.** The host exposes the relay with `tailscale serve`, which reaches teammates on the tailnet, or `tailscale funnel`, which reaches people outside it. Both give the browser the https address it needs. +- **The team's own relay.** `abcd session blueprint` writes a Render Blueprint and a Dockerfile under `.abcd/relay/`. The Dockerfile fetches the released abcd binary for linux/amd64 and checks it against the release's checksum before running the same verb. One deploy serves all of the team's sessions. + +The relay holds: +- each session's Yjs updates, as opaque bytes, in memory only. A session's log is dropped when the host closes the session, or when nobody has been connected for the idle limit. +- the team's GitHub sign-in app secret, read from the environment by name. The secret is never logged and never sent to a browser. + +The relay never parses the brief and never writes to disk. + +**The wire.** A JSON control message is a text frame. A Yjs update or awareness message is a binary frame with a one-byte tag. These messages exist: +- `host`: a team member opens a session for owner/repo. The relay answers with a six-character join code from rascals' 31-symbol alphabet. The code works while the host keeps it open, and the host can rotate it. +- `join`: anyone presents the code. Failed joins are rate-limited per address, and the forwarded address is trusted only behind a declared proxy. +- `verify`: a participant presents a GitHub token. The relay asks GitHub for the login, and for the repository's `permissions.push`. A participant with write access or above becomes a team member, and the relay drops the token straight after the check. + +The relay replays the session's update log to every newcomer. It then: +- forwards Yjs updates only from team members. A guest's binary updates are refused, which is how guests stay read-only on the text. +- forwards a guest's `suggest` message to the team members, stamped with the guest's display name. +- broadcasts the roster of verified identities. + +Limits bound all of this: message size, log bytes, participants per session, sessions per relay and idle time. Origins are checked against an allowlist, with abcdesign.app and loopback as the defaults. + +**Sign-in.** The page builds the GitHub authorize URL from the relay's public client id, with PKCE and a state value it keeps in session storage. The redirect comes back to the page. The page posts the code and the PKCE verifier to `/auth/token`, and the relay adds the secret and does the exchange: this is the token-mediating backend in the IETF's browser-based-apps guidance. The token stays in the page's memory. Pasting a personal access token is the fallback, and the same `verify` admits it. + +**The page.** abcd generates the page itself. `abcd session page --out ` writes plain files from assets embedded in the binary: +- one HTML file, served for every path through GitHub Pages' 404 fallback, which is how `///brief/` resolves on a static host +- the page script +- the stylesheet +- `collab.js`, a vendored bundle + +`collab.js` holds Yjs 13.6.33, y-codemirror.next 0.3.6, y-protocols 1.0.7, CodeMirror 6 with Markdown, and qrcode-generator 2.0.4, all MIT. It was built outside the repository with esbuild 0.28.2. Every package version is at least two weeks old. Its provenance (each package, version and npm integrity hash, the entry module and the bundle's SHA-256) is committed beside it, and a Go test fails if the bytes drift. No Node toolchain enters the repository, consistent with adr-47. + +The host's page loads the brief through the GitHub API with the host's token. It records the default branch's commit as the session's base and seeds one Y.Text per brief file exactly once, before any invite. It then shows a QR code that encodes the page URL, with the relay address and the code in the URL fragment, so neither reaches GitHub's servers. + +Participants: +- see each other's edits and cursors live (awareness) +- if they are guests, propose a replacement for a selected passage; a team member applies or declines it, and the record names the approver + +The document has `gc` off, so each change can be credited. Credit comes from the Yjs client ids each member's page registers in the roster. + +**Back to GitHub.** The host picks the route per session. +- **Issues.** Each changed passage becomes a pre-filled issue link, carrying: + - the file path + - the passage before and after + - the base commit + - the credited author, or the approver and the guest + + The link's length is measured as GitHub receives it, after encoding, against a margin under 8 KB. A longer passage is offered as a pull request instead. +- **One pull request.** The submitter's token calls `createRef`, then `createCommitOnBranch` (the commit's author is the submitter), then `createPullRequest`. The commit message carries a `Co-authored-by:` line for every credited team member. For each approved guest change it carries a `Suggested-by:` line naming the guest, with the `Approved-by:` line beside it. + +Every request in a session goes to the team's relay, to GitHub, or to the static page's host. None reaches a server abcd runs. + +The hosting of abcdesign.app is recorded in the plan but not made in this change. Deploying the page to GitHub Pages under the abcdesign.app domain publishes, so it waits for the product thinker's go at the cut. ## Footprint -- packages: -- tests: +- packages: internal/core/session (new), internal/surface/session (new), internal/surface/cli, commands/session.md, docs/ +- tests: internal/core/session, internal/surface/session, internal/surface/cli, evals/ smoke ## Steps -_No steps listed: the spec is built as one step. To split it, list the steps in order as `1. `, each with `- packages:` and `- tests:` indented beneath it; `- landed: <pull request or commit>` marks a step that has landed._ +1. The relay: session rooms, join codes, roles, update log, suggestions, sign-in exchange and limits, behind `abcd session serve`, with the WebSocket library github.com/coder/websocket v1.8.15 (ISC, no dependencies) + - packages: internal/core/session, internal/surface/session, internal/surface/cli, commands/session.md + - tests: internal/core/session, internal/surface/session +2. The relay blueprint and the Tailscale route: `abcd session blueprint` writes `.abcd/relay/render.yaml` and `.abcd/relay/Dockerfile`, pinned to a released binary and its checksum + - packages: internal/core/session, internal/surface/cli, docs/ + - tests: internal/core/session, internal/surface/cli +3. The page: `abcd session page --out` emits the static page and the vendored, provenance-pinned editor bundle; joining by QR or code, live editing, presence, guest suggestions and approvals, and credit + - packages: internal/surface/session/web, internal/surface/cli + - tests: internal/surface/session +4. Back to GitHub: issue links under the measured length limit, and one pull request with co-author, Suggested-by and Approved-by lines + - packages: internal/surface/session/web + - tests: internal/surface/session diff --git a/.abcd/work/issues/open/iss-2610141947553722-abcd-lint-s-privacy-hygiene-rule-reads-a.md b/.abcd/work/issues/open/iss-2610141947553722-abcd-lint-s-privacy-hygiene-rule-reads-a.md new file mode 100644 index 0000000000..a1e339459f --- /dev/null +++ b/.abcd/work/issues/open/iss-2610141947553722-abcd-lint-s-privacy-hygiene-rule-reads-a.md @@ -0,0 +1,16 @@ +--- +schema_version: 1 +id: "iss-2610141947553722" +slug: "abcd-lint-s-privacy-hygiene-rule-reads-a" +severity: "minor" +category: "bug" +source: "agent-finding" +found_during: "hosted-sites build, bare abcd lint run" +origin: researcher-authored +production_mode: hand-written +found_at: ".abcd/development/research/notes/2026-10-14-abcdev-app-github-builds-and-cache.md" +remedy: "Treat a dotted number as an address only where its context reads as one (not after 'section', '§' or 'RFC N'), or skip prose files' section citations, with a test on '[redacted-address]'" +refines: [iss-2609290845269642] +--- + +abcd lint's privacy-hygiene rule reads a dotted RFC section number such as [redacted-address] in prose as a non-reserved IPv4 address and fails the file From 6e8e7ceff67692c81506326050b003ec4c14a29b Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:48:32 +0100 Subject: [PATCH 002/165] chore: vendor the brief session's editor bundle with its provenance collab.js bundles Yjs 13.6.33, y-codemirror.next 0.3.6, y-protocols 1.0.7, CodeMirror 6 with Markdown and qrcode-generator 2.0.4, all MIT, resolved to versions published before 2026-09-29 and built outside the repository with esbuild 0.28.2, so no Node toolchain enters it (adr-47). PROVENANCE.json names every package, version and npm integrity hash and the bundle's SHA-256; LICENSES.txt carries each licence. Assisted-by: Claude:claude-opus-5-5 --- .../surface/session/web/vendor/LICENSES.txt | 655 ++++++++++++++++++ .../session/web/vendor/PROVENANCE.json | 218 ++++++ .../session/web/vendor/collab.entry.js | 9 + internal/surface/session/web/vendor/collab.js | 36 + 4 files changed, 918 insertions(+) create mode 100644 internal/surface/session/web/vendor/LICENSES.txt create mode 100644 internal/surface/session/web/vendor/PROVENANCE.json create mode 100644 internal/surface/session/web/vendor/collab.entry.js create mode 100644 internal/surface/session/web/vendor/collab.js diff --git a/internal/surface/session/web/vendor/LICENSES.txt b/internal/surface/session/web/vendor/LICENSES.txt new file mode 100644 index 0000000000..9c3344ad19 --- /dev/null +++ b/internal/surface/session/web/vendor/LICENSES.txt @@ -0,0 +1,655 @@ +Third-party licences for collab.js. Every package below is bundled into it. + +== @codemirror/autocomplete@6.20.3 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/commands@6.11.1 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/lang-css@6.3.1 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/lang-html@6.4.12 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/lang-javascript@6.2.5 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/lang-markdown@6.5.2 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/language@6.12.4 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/lint@6.9.7 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/search@6.7.2 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/state@6.7.6 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @codemirror/view@6.43.13 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/common@1.5.3 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/css@1.3.8 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/highlight@1.2.5 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/html@1.3.13 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/javascript@1.5.5 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/lr@1.4.10 == +MIT License + +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @lezer/markdown@1.7.2 == +MIT License + +Copyright (C) 2020 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== @marijn/find-cluster-break@1.0.4 == +MIT License + +Copyright (C) 2024 by Marijn Haverbeke <marijn@haverbeke.berlin> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== codemirror@6.0.2 == +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== crelt@1.0.7 == +Copyright (C) 2020 by Marijn Haverbeke <marijn@haverbeke.berlin> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== isomorphic.js@0.2.5 == +The MIT License (MIT) + +Copyright (c) 2020 Kevin Jahns <kevin.jahns@protonmail.com>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +== lib0@0.2.119 == +The MIT License (MIT) + +Copyright (c) 2019 Kevin Jahns <kevin.jahns@protonmail.com>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +== qrcode-generator@2.0.4 == +MIT. The package ships no licence file; its source header reads: + +--------------------------------------------------------------------- +QR Code Generator for JavaScript +Copyright (c) 2009 Kazuhiko Arase +URL: http://www.d-project.com/ +Licensed under the MIT license: +http://www.opensource.org/licenses/mit-license.php +The word 'QR Code' is registered trademark of +DENSO WAVE INCORPORATED +http://www.denso-wave.com/qrcode/faqpatent-e.html + +== style-mod@4.1.4 == +Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== w3c-keyname@2.2.8 == +Copyright (C) 2016 by Marijn Haverbeke <marijn@haverbeke.berlin> and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +== y-codemirror.next@0.3.6 == +The MIT License (MIT) + +Copyright (c) 2024 + - Kevin Jahns <kevin.jahns@protonmail.com>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +== y-protocols@1.0.7 == +The MIT License (MIT) + +Copyright (c) 2019 Kevin Jahns <kevin.jahns@protonmail.com>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +== yjs@13.6.33 == +The MIT License (MIT) + +Copyright (c) 2023 + - Kevin Jahns <kevin.jahns@protonmail.com>. + - Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/internal/surface/session/web/vendor/PROVENANCE.json b/internal/surface/session/web/vendor/PROVENANCE.json new file mode 100644 index 0000000000..ca3bc03f75 --- /dev/null +++ b/internal/surface/session/web/vendor/PROVENANCE.json @@ -0,0 +1,218 @@ +{ + "bundle": "collab.js", + "sha256": "13ace93e6ad0d43d08ef4faa8f394af72504c52c682f903acdcc3966ac89fc7a", + "entry": "collab.entry.js", + "entry_sha256": "645e77d6443a4b3511473ae18ca4553ffd732e5a2f023abb440ec4b5a8b6044c", + "bundler": { + "name": "esbuild", + "version": "0.28.2" + }, + "command": "esbuild collab.entry.js --bundle --format=esm --minify --target=es2020 --legal-comments=eof --outfile=collab.js", + "resolved_before": "2026-09-29", + "install": "npm install --ignore-scripts --before=2026-09-29", + "packages": [ + { + "name": "@codemirror/autocomplete", + "version": "6.20.3", + "license": "MIT", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz" + }, + { + "name": "@codemirror/commands", + "version": "6.11.1", + "license": "MIT", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz" + }, + { + "name": "@codemirror/lang-css", + "version": "6.3.1", + "license": "MIT", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz" + }, + { + "name": "@codemirror/lang-html", + "version": "6.4.12", + "license": "MIT", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz" + }, + { + "name": "@codemirror/lang-javascript", + "version": "6.2.5", + "license": "MIT", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz" + }, + { + "name": "@codemirror/lang-markdown", + "version": "6.5.2", + "license": "MIT", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz" + }, + { + "name": "@codemirror/language", + "version": "6.12.4", + "license": "MIT", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz" + }, + { + "name": "@codemirror/lint", + "version": "6.9.7", + "license": "MIT", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz" + }, + { + "name": "@codemirror/search", + "version": "6.7.2", + "license": "MIT", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz" + }, + { + "name": "@codemirror/state", + "version": "6.7.6", + "license": "MIT", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz" + }, + { + "name": "@codemirror/view", + "version": "6.43.13", + "license": "MIT", + "integrity": "sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.13.tgz" + }, + { + "name": "@lezer/common", + "version": "1.5.3", + "license": "MIT", + "integrity": "sha512-H0iErY4e43LpXbYDyBci5W4v/RwTgGV3YzYOlJPiGZ8RY8w51kVE+xLn1tg3Z6UmSfyETvtsUK3r7YsgDQEI7Q==", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.3.tgz" + }, + { + "name": "@lezer/css", + "version": "1.3.8", + "license": "MIT", + "integrity": "sha512-EJn1zcL9qoDptief6ipWKZKLiOpXkxSe0+t8CH9oiMVcZlq7NBWrjCqnc/41EIjeo/ITj1gFFiATdTkaJDL+Og==", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.8.tgz" + }, + { + "name": "@lezer/highlight", + "version": "1.2.5", + "license": "MIT", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz" + }, + { + "name": "@lezer/html", + "version": "1.3.13", + "license": "MIT", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz" + }, + { + "name": "@lezer/javascript", + "version": "1.5.5", + "license": "MIT", + "integrity": "sha512-sWg4yX1J6XW67AaAynVt0iwF0M5c+np36TEu+P2ifAJ8haRYvHnWDV28r1jdwnJehWCwXECutAUy56K4RBZIyg==", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.5.tgz" + }, + { + "name": "@lezer/lr", + "version": "1.4.10", + "license": "MIT", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz" + }, + { + "name": "@lezer/markdown", + "version": "1.7.2", + "license": "MIT", + "integrity": "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.2.tgz" + }, + { + "name": "@marijn/find-cluster-break", + "version": "1.0.4", + "license": "MIT", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz" + }, + { + "name": "codemirror", + "version": "6.0.2", + "license": "MIT", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz" + }, + { + "name": "crelt", + "version": "1.0.7", + "license": "MIT", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz" + }, + { + "name": "isomorphic.js", + "version": "0.2.5", + "license": "MIT", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz" + }, + { + "name": "lib0", + "version": "0.2.119", + "license": "MIT", + "integrity": "sha512-vh+TiejVy8Xm+hCdnRfn9BthGNvNUZdEXltNjVZL3TzRIKP3s1lnQh8pWd/Q0wCgY0FPAGVVjFF4ZiHUV3LFgA==", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.119.tgz" + }, + { + "name": "qrcode-generator", + "version": "2.0.4", + "license": "MIT", + "integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz" + }, + { + "name": "style-mod", + "version": "4.1.4", + "license": "MIT", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz" + }, + { + "name": "w3c-keyname", + "version": "2.2.8", + "license": "MIT", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz" + }, + { + "name": "y-codemirror.next", + "version": "0.3.6", + "license": "MIT", + "integrity": "sha512-GnmVXhTe+UtoFbbaSdwhq6gdAQZdIY9az0Xj6HR2J4PO6Yw4w3xaaSssU+6T72WhXAI0wf9FbuY2s0Ms+WrexA==", + "resolved": "https://registry.npmjs.org/y-codemirror.next/-/y-codemirror.next-0.3.6.tgz" + }, + { + "name": "y-protocols", + "version": "1.0.7", + "license": "MIT", + "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz" + }, + { + "name": "yjs", + "version": "13.6.33", + "license": "MIT", + "integrity": "sha512-q/UYvr1gOk4XII+Cu1CW2dINjYqJJFifTzogEaIARznrvX7uOlHsdTEXcnOBMuIsrCGEEKdNxi74kyMLZlKVfw==", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.33.tgz" + } + ] +} diff --git a/internal/surface/session/web/vendor/collab.entry.js b/internal/surface/session/web/vendor/collab.entry.js new file mode 100644 index 0000000000..fb3f768cba --- /dev/null +++ b/internal/surface/session/web/vendor/collab.entry.js @@ -0,0 +1,9 @@ +// The browser editor's third-party code, bundled once into collab.js. +export * as Y from "yjs"; +export { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate, removeAwarenessStates } from "y-protocols/awareness"; +export { yCollab, yUndoManagerKeymap } from "y-codemirror.next"; +export { EditorView, basicSetup } from "codemirror"; +export { EditorState } from "@codemirror/state"; +export { keymap } from "@codemirror/view"; +export { markdown } from "@codemirror/lang-markdown"; +export { default as qrcode } from "qrcode-generator"; diff --git a/internal/surface/session/web/vendor/collab.js b/internal/surface/session/web/vendor/collab.js new file mode 100644 index 0000000000..efa8dd9f49 --- /dev/null +++ b/internal/surface/session/web/vendor/collab.js @@ -0,0 +1,36 @@ +var xO=Object.defineProperty;var xw=(i,e,t)=>e in i?xO(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t;var ww=(i,e)=>{for(var t in e)xO(i,t,{get:e[t],enumerable:!0})};var ol=(i,e,t)=>xw(i,typeof e!="symbol"?e+"":e,t);var _r={};ww(_r,{AbsolutePosition:()=>Ul,AbstractConnector:()=>Nh,AbstractStruct:()=>Mr,AbstractType:()=>ge,Array:()=>vn,ContentAny:()=>Di,ContentBinary:()=>Tn,ContentDeleted:()=>Rr,ContentDoc:()=>An,ContentEmbed:()=>mi,ContentFormat:()=>me,ContentJSON:()=>Gs,ContentString:()=>Dt,ContentType:()=>Pt,Doc:()=>_i,GC:()=>Be,ID:()=>Oi,Item:()=>le,Map:()=>$n,PermanentUserData:()=>Gh,RelativePosition:()=>Tr,Skip:()=>Xe,Snapshot:()=>Us,Text:()=>Ar,Transaction:()=>Vl,UndoManager:()=>Bs,UpdateDecoderV1:()=>ot,UpdateDecoderV2:()=>Fe,UpdateEncoderV1:()=>ri,UpdateEncoderV2:()=>Ot,XmlElement:()=>Cn,XmlFragment:()=>Pn,XmlHook:()=>qs,XmlText:()=>Fl,YArrayEvent:()=>Wl,YEvent:()=>Qn,YMapEvent:()=>Nl,YTextEvent:()=>ql,YXmlEvent:()=>Gl,applyUpdate:()=>eQ,applyUpdateV2:()=>Jl,cleanupYTextFormatting:()=>gm,compareIDs:()=>yn,compareRelativePositions:()=>ta,convertUpdateFormatV1ToV2:()=>XQ,convertUpdateFormatV2ToV1:()=>Jg,createAbsolutePositionFromRelativePosition:()=>Er,createDeleteSet:()=>Kl,createDeleteSetFromStructStore:()=>sf,createDocFromSnapshot:()=>bQ,createID:()=>W,createRelativePositionFromJSON:()=>bi,createRelativePositionFromTypeIndex:()=>Xr,createSnapshot:()=>ff,decodeRelativePosition:()=>fQ,decodeSnapshot:()=>OQ,decodeSnapshotV2:()=>Ig,decodeStateVector:()=>af,decodeUpdate:()=>$Q,decodeUpdateV2:()=>Ng,diffUpdate:()=>AQ,diffUpdateV2:()=>uf,emptySnapshot:()=>gQ,encodeRelativePosition:()=>cQ,encodeSnapshot:()=>pQ,encodeSnapshotV2:()=>jg,encodeStateAsUpdate:()=>iQ,encodeStateAsUpdateV2:()=>_g,encodeStateVector:()=>sQ,encodeStateVectorFromUpdate:()=>PQ,encodeStateVectorFromUpdateV2:()=>Gg,equalDeleteSets:()=>Eg,equalSnapshots:()=>dQ,findIndexSS:()=>$t,findRootTypeKey:()=>hf,getItem:()=>Sn,getItemCleanEnd:()=>Kh,getItemCleanStart:()=>Ue,getState:()=>ue,getTypeChildren:()=>LQ,isDeleted:()=>Mn,isParentOf:()=>Is,iterateDeletedStructs:()=>xn,logType:()=>oQ,logUpdate:()=>vQ,logUpdateV2:()=>Wg,mergeDeleteSets:()=>wn,mergeUpdates:()=>qg,mergeUpdatesV2:()=>Ys,obfuscateUpdate:()=>MQ,obfuscateUpdateV2:()=>RQ,parseUpdateMeta:()=>CQ,parseUpdateMetaV2:()=>Fg,readUpdate:()=>Jk,readUpdateV2:()=>lf,relativePositionToJSON:()=>ea,snapshot:()=>mQ,snapshotContainsUpdate:()=>SQ,transact:()=>oe,tryGc:()=>kQ,typeListToArraySnapshot:()=>DQ,typeMapGetAllSnapshot:()=>fm,typeMapGetSnapshot:()=>UQ});var pe=()=>new Map,ll=i=>{let e=pe();return i.forEach((t,n)=>{e.set(n,t)}),e},Ie=(i,e,t)=>{let n=i.get(e);return n===void 0&&i.set(e,n=t()),n},wO=(i,e)=>{let t=[];for(let[n,r]of i)t.push(e(r,n));return t},kO=(i,e)=>{for(let[t,n]of i)if(e(n,t))return!0;return!1};var Ht=()=>new Set;var al=i=>i[i.length-1];var QO=(i,e)=>{for(let t=0;t<e.length;t++)i.push(e[t])},_t=Array.from,cl=(i,e)=>{for(let t=0;t<i.length;t++)if(!e(i[t],t,i))return!1;return!0},ys=(i,e)=>{for(let t=0;t<i.length;t++)if(e(i[t],t,i))return!0;return!1};var vO=(i,e)=>{let t=new Array(i);for(let n=0;n<i;n++)t[n]=e(n,t);return t};var ui=Array.isArray;var sr=class{constructor(){this._observers=pe()}on(e,t){return Ie(this._observers,e,Ht).add(t),t}once(e,t){let n=(...r)=>{this.off(e,n),t(...r)};this.on(e,n)}off(e,t){let n=this._observers.get(e);n!==void 0&&(n.delete(t),n.size===0&&this._observers.delete(e))}emit(e,t){return _t((this._observers.get(e)||pe()).values()).forEach(n=>n(...t))}destroy(){this._observers=pe()}},hl=class{constructor(){this._observers=pe()}on(e,t){Ie(this._observers,e,Ht).add(t)}once(e,t){let n=(...r)=>{this.off(e,n),t(...r)};this.on(e,n)}off(e,t){let n=this._observers.get(e);n!==void 0&&(n.delete(t),n.size===0&&this._observers.delete(e))}emit(e,t){return _t((this._observers.get(e)||pe()).values()).forEach(n=>n(...t))}destroy(){this._observers=pe()}};var Re=Math.floor;var un=Math.abs;var or=(i,e)=>i<e?i:e,Lt=(i,e)=>i>e?i:e,uR=Number.isNaN;var fl=i=>i!==0?i<0:1/i<0;var lr=Number.MAX_SAFE_INTEGER,lh=Number.MIN_SAFE_INTEGER,dR=1<<31;var $O=Number.isInteger||(i=>typeof i=="number"&&isFinite(i)&&Re(i)===i),pR=Number.isNaN,OR=Number.parseInt;var ch=String.fromCharCode,kw=String.fromCodePoint,gR=ch(65535),Qw=i=>i.toLowerCase(),vw=/^\s*/g,$w=i=>i.replace(vw,""),Pw=/([A-Z])/g,hh=(i,e)=>$w(i.replace(Pw,t=>`${e}${Qw(t)}`));var Cw=i=>{let e=unescape(encodeURIComponent(i)),t=e.length,n=new Uint8Array(t);for(let r=0;r<t;r++)n[r]=e.codePointAt(r);return n},cr=typeof TextEncoder<"u"?new TextEncoder:null,Tw=i=>cr.encode(i),PO=cr?Tw:Cw;var ar=typeof TextDecoder>"u"?null:new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0});ar&&ar.decode(new Uint8Array).length===1&&(ar=null);var ul=(i,e)=>vO(e,()=>i).join("");var pn=class{constructor(){this.cpos=0,this.cbuf=new Uint8Array(100),this.bufs=[]}},di=()=>new pn;var Aw=i=>{let e=i.cpos;for(let t=0;t<i.bufs.length;t++)e+=i.bufs[t].length;return e};var qe=i=>{let e=new Uint8Array(Aw(i)),t=0;for(let n=0;n<i.bufs.length;n++){let r=i.bufs[n];e.set(r,t),t+=r.length}return e.set(new Uint8Array(i.cbuf.buffer,0,i.cpos),t),e},Mw=(i,e)=>{let t=i.cbuf.length;t-i.cpos<e&&(i.bufs.push(new Uint8Array(i.cbuf.buffer,0,i.cpos)),i.cbuf=new Uint8Array(Lt(t,e)*2),i.cpos=0)},Pe=(i,e)=>{let t=i.cbuf.length;i.cpos===t&&(i.bufs.push(i.cbuf),i.cbuf=new Uint8Array(t*2),i.cpos=0),i.cbuf[i.cpos++]=e};var dr=Pe;var B=(i,e)=>{for(;e>127;)Pe(i,128|127&e),e=Re(e/128);Pe(i,127&e)},ks=(i,e)=>{let t=fl(e);for(t&&(e=-e),Pe(i,(e>63?128:0)|(t?64:0)|63&e),e=Re(e/64);e>0;)Pe(i,(e>127?128:0)|127&e),e=Re(e/128)},fh=new Uint8Array(3e4),Rw=fh.length/3,Xw=(i,e)=>{if(e.length<Rw){let t=cr.encodeInto(e,fh).written||0;B(i,t);for(let n=0;n<t;n++)Pe(i,fh[n])}else rt(i,PO(e))},Ew=(i,e)=>{let t=unescape(encodeURIComponent(e)),n=t.length;B(i,n);for(let r=0;r<n;r++)Pe(i,t.codePointAt(r))},Kt=cr&&cr.encodeInto?Xw:Ew;var RO=(i,e)=>pr(i,qe(e)),pr=(i,e)=>{let t=i.cbuf.length,n=i.cpos,r=or(t-n,e.length),s=e.length-r;i.cbuf.set(e.subarray(0,r),n),i.cpos+=r,s>0&&(i.bufs.push(i.cbuf),i.cbuf=new Uint8Array(Lt(t*2,s)),i.cbuf.set(e.subarray(r)),i.cpos=s)},rt=(i,e)=>{B(i,e.byteLength),pr(i,e)},uh=(i,e)=>{Mw(i,e);let t=new DataView(i.cbuf.buffer,i.cpos,e);return i.cpos+=e,t},Zw=(i,e)=>uh(i,4).setFloat32(0,e,!1),_w=(i,e)=>uh(i,8).setFloat64(0,e,!1),Lw=(i,e)=>uh(i,8).setBigInt64(0,e,!1);var TO=new DataView(new ArrayBuffer(4)),Dw=i=>(TO.setFloat32(0,i),TO.getFloat32(0)===i),fr=(i,e)=>{switch(typeof e){case"string":Pe(i,119),Kt(i,e);break;case"number":$O(e)&&un(e)<=2147483647?(Pe(i,125),ks(i,e)):Dw(e)?(Pe(i,124),Zw(i,e)):(Pe(i,123),_w(i,e));break;case"bigint":Pe(i,122),Lw(i,e);break;case"object":if(e===null)Pe(i,126);else if(ui(e)){Pe(i,117),B(i,e.length);for(let t=0;t<e.length;t++)fr(i,e[t])}else if(e instanceof Uint8Array)Pe(i,116),rt(i,e);else{Pe(i,118);let t=Object.keys(e);B(i,t.length);for(let n=0;n<t.length;n++){let r=t[n];Kt(i,r),fr(i,e[r])}}break;case"boolean":Pe(i,e?120:121);break;default:Pe(i,127)}},ws=class extends pn{constructor(e){super(),this.w=e,this.s=null,this.count=0}write(e){this.s===e?this.count++:(this.count>0&&B(this,this.count-1),this.count=1,this.w(this,e),this.s=e)}};var AO=i=>{i.count>0&&(ks(i.encoder,i.count===1?i.s:-i.s),i.count>1&&B(i.encoder,i.count-2))},On=class{constructor(){this.encoder=new pn,this.s=0,this.count=0}write(e){this.s===e?this.count++:(AO(this),this.count=1,this.s=e)}toUint8Array(){return AO(this),qe(this.encoder)}};var MO=i=>{if(i.count>0){let e=i.diff*2+(i.count===1?0:1);ks(i.encoder,e),i.count>1&&B(i.encoder,i.count-2)}},ur=class{constructor(){this.encoder=new pn,this.s=0,this.count=0,this.diff=0}write(e){this.diff===e-this.s?(this.s=e,this.count++):(MO(this),this.count=1,this.diff=e-this.s,this.s=e)}toUint8Array(){return MO(this),qe(this.encoder)}},dl=class{constructor(){this.sarr=[],this.s="",this.lensE=new On}write(e){this.s+=e,this.s.length>19&&(this.sarr.push(this.s),this.s=""),this.lensE.write(e.length)}toUint8Array(){let e=new pn;return this.sarr.push(this.s),this.s="",Kt(e,this.sarr.join("")),pr(e,this.lensE.toUint8Array()),qe(e)}};var dt=i=>new Error(i),pt=()=>{throw dt("Method unimplemented")},Ce=()=>{throw dt("Unexpected case")};var Ol=dt("Unexpected end of array"),EO=dt("Integer out of Range"),jw=(i,e,t)=>{if(e<0||t<0||e+t>i.length)throw Ol;return new Uint8Array(i.buffer,i.byteOffset+e,t)},Iw=(i,e,t)=>{if(e<0||t<0||e+t>i.length)throw Ol;return new DataView(i.buffer,i.byteOffset+e,t)},Or=class{constructor(e){this.arr=e,this.pos=0}},Qe=i=>new Or(i),ph=i=>i.pos!==i.arr.length;var Uw=(i,e)=>{let t=jw(i.arr,i.pos,e);return i.pos+=e,t},st=i=>Uw(i,z(i));var gn=i=>i.arr[i.pos++];var z=i=>{let e=0,t=1,n=i.arr.length;for(;i.pos<n;){let r=i.arr[i.pos++];if(e=e+(r&127)*t,t*=128,r<128)return e;if(e>lr)throw EO}throw Ol},vs=i=>{let e=i.arr[i.pos++],t=e&63,n=64,r=(e&64)>0?-1:1;if((e&128)===0)return r*t;let s=i.arr.length;for(;i.pos<s;){if(e=i.arr[i.pos++],t=t+(e&127)*n,n*=128,e<128)return r*t;if(t>lr)throw EO}throw Ol};var Bw=i=>{let e=z(i);if(e===0)return"";{let t=String.fromCodePoint(gn(i));if(--e<100)for(;e--;)t+=String.fromCodePoint(gn(i));else for(;e>0;){let n=e<1e4?e:1e4,r=i.arr.subarray(i.pos,i.pos+n);i.pos+=n,t+=String.fromCodePoint.apply(null,r),e-=n}return decodeURIComponent(escape(t))}},Vw=i=>ar.decode(st(i)),Jt=ar?Vw:Bw;var Oh=(i,e)=>{let t=Iw(i.arr,i.pos,e);return i.pos+=e,t},Yw=i=>Oh(i,4).getFloat32(0,!1),zw=i=>Oh(i,8).getFloat64(0,!1),Ww=i=>Oh(i,8).getBigInt64(0,!1);var Nw=[i=>{},i=>null,vs,Yw,zw,Ww,i=>!1,i=>!0,Jt,i=>{let e=z(i),t={};for(let n=0;n<e;n++){let r=Jt(i);t[r]=gr(i)}return t},i=>{let e=z(i),t=[];for(let n=0;n<e;n++)t.push(gr(i));return t},st],gr=i=>Nw[127-gn(i)](i),Qs=class extends Or{constructor(e,t){super(e),this.reader=t,this.s=null,this.count=0}read(){return this.count===0&&(this.s=this.reader(this),ph(this)?this.count=z(this)+1:this.count=-1),this.count--,this.s}};var mn=class extends Or{constructor(e){super(e),this.s=0,this.count=0}read(){if(this.count===0){this.s=vs(this);let e=fl(this.s);this.count=1,e&&(this.s=-this.s,this.count=z(this)+2)}return this.count--,this.s}};var mr=class extends Or{constructor(e){super(e),this.s=0,this.count=0,this.diff=0}read(){if(this.count===0){let e=vs(this),t=e&1;this.diff=Re(e/2),this.count=1,t&&(this.count=z(this)+2)}return this.s+=this.diff,this.count--,this.s}},pl=class{constructor(e){this.decoder=new mn(e),this.str=Jt(this.decoder),this.spos=0}read(){let e=this.spos+this.decoder.read(),t=this.str.slice(this.spos,e);return this.spos=e,t}};var yR=crypto.subtle,_O=crypto.getRandomValues.bind(crypto);var gh=()=>_O(new Uint32Array(1))[0];var qw="10000000-1000-4000-8000"+-1e11,LO=()=>qw.replace(/[018]/g,i=>(i^gh()&15>>i/4).toString(16));var pi=Date.now;var bh=i=>new Promise(i);var wR=Promise.all.bind(Promise);var yh=i=>i===void 0?null:i;var Sh=class{constructor(){this.map=new Map}setItem(e,t){this.map.set(e,t)}getItem(e){return this.map.get(e)}},DO=new Sh,Kw=!0;try{typeof localStorage<"u"&&localStorage&&(DO=localStorage,Kw=!1)}catch{}var jO=DO;var bn=Symbol("Equality"),gl=(i,e)=>i===e||!!i?.[bn]?.(e)||!1;var IO=i=>typeof i=="object",UO=Object.assign,ek=Object.keys;var BO=(i,e)=>{for(let t in i)e(i[t],t)};var $s=i=>ek(i).length;var VO=i=>{for(let e in i)return!1;return!0},br=(i,e)=>{for(let t in i)if(!e(i[t],t))return!1;return!0},Ps=(i,e)=>Object.prototype.hasOwnProperty.call(i,e),wh=(i,e)=>i===e||$s(i)===$s(e)&&br(i,(t,n)=>(t!==void 0||Ps(e,n))&&gl(e[n],t)),tk=Object.freeze,kh=i=>{for(let e in i){let t=i[e];(typeof t=="object"||typeof t=="function")&&kh(i[e])}return tk(i)};var Cs=(i,e,t=0)=>{try{for(;t<i.length;t++)i[t](...e)}finally{t<i.length&&Cs(i,e,t+1)}};var vh=i=>i;var Mi=(i,e)=>{if(i===e)return!0;if(i==null||e==null||i.constructor!==e.constructor&&(i.constructor||Object)!==(e.constructor||Object))return!1;if(i[bn]!=null)return i[bn](e);switch(i.constructor){case ArrayBuffer:i=new Uint8Array(i),e=new Uint8Array(e);case Uint8Array:{if(i.byteLength!==e.byteLength)return!1;for(let t=0;t<i.length;t++)if(i[t]!==e[t])return!1;break}case Set:{if(i.size!==e.size)return!1;for(let t of i)if(!e.has(t))return!1;break}case Map:{if(i.size!==e.size)return!1;for(let t of i.keys())if(!e.has(t)||!Mi(i.get(t),e.get(t)))return!1;break}case void 0:case Object:if($s(i)!==$s(e))return!1;for(let t in i)if(!Ps(i,t)||!Mi(i[t],e[t]))return!1;break;case Array:if(i.length!==e.length)return!1;for(let t=0;t<i.length;t++)if(!Mi(i[t],e[t]))return!1;break;default:return!1}return!0},YO=(i,e)=>e.includes(i);var Ts=typeof process<"u"&&process.release&&/node|io\.js/.test(process.release.name)&&Object.prototype.toString.call(typeof process<"u"?process:0)==="[object process]";var kR=typeof navigator<"u"?/Mac/.test(navigator.platform):!1,ei,ik=[],nk=()=>{if(ei===void 0)if(Ts){ei=pe();let i=process.argv,e=null;for(let t=0;t<i.length;t++){let n=i[t];n[0]==="-"?(e!==null&&ei.set(e,""),e=n):e!==null?(ei.set(e,n),e=null):ik.push(n)}e!==null&&ei.set(e,"")}else typeof location=="object"?(ei=pe(),(location.search||"?").slice(1).split("&").forEach(i=>{if(i.length!==0){let[e,t]=i.split("=");ei.set(`--${hh(e,"-")}`,t),ei.set(`-${hh(e,"-")}`,t)}})):ei=pe();return ei},$h=i=>nk().has(i);var As=i=>Ts?yh(process.env[i.toUpperCase().replaceAll("-","_")]):yh(jO.getItem(i));var zO=i=>$h("--"+i)||As(i)!==null,WO=zO("production"),rk=Ts&&YO(process.env.FORCE_COLOR,["true","1","2"]),NO=rk||!$h("--no-colors")&&!zO("no-color")&&(!Ts||process.stdout.isTTY)&&(!Ts||$h("--color")||As("COLORTERM")!==null||(As("TERM")||"").includes("color"));var sk=i=>new Uint8Array(i);var qO=i=>{let e=sk(i.byteLength);return e.set(i),e};var Ch=class{constructor(e,t){this.left=e,this.right=t}},Ge=(i,e)=>new Ch(i,e);var GO=(i,e)=>i.forEach(t=>e(t.left,t.right));var Ah=i=>i.next()>=.5,bl=(i,e,t)=>Re(i.next()*(t+1-e)+e);var Mh=(i,e,t)=>Re(i.next()*(t+1-e)+e);var Rh=(i,e,t)=>Mh(i,e,t);var lk=i=>ch(Rh(i,97,122)),FO=(i,e=0,t=20)=>{let n=Rh(i,e,t),r="";for(let s=0;s<n;s++)r+=lk(i);return r};var yl=(i,e)=>e[Rh(i,0,e.length-1)];var ck=Symbol("0schema"),Xh=class{constructor(){this._rerrs=[]}extend(e,t,n,r=null){this._rerrs.push({path:e,expected:t,has:n,message:r})}toString(){let e=[];for(let t=this._rerrs.length-1;t>0;t--){let n=this._rerrs[t];e.push(ul(" ",(this._rerrs.length-t)*2)+`${n.path!=null?`[${n.path}] `:""}${n.has} doesn't match ${n.expected}. ${n.message}`)}return e.join(` +`)}},Eh=(i,e)=>i===e?!0:i==null||e==null||i.constructor!==e.constructor?!1:i[bn]?gl(i,e):ui(i)?cl(i,t=>ys(e,n=>Eh(t,n))):IO(i)?br(i,(t,n)=>Eh(t,e[n])):!1,Te=class{extends(e){let[t,n]=[this.shape,e.shape];return this.constructor._dilutes&&([n,t]=[t,n]),Eh(t,n)}equals(e){return this.constructor===e.constructor&&Mi(this.shape,e.shape)}[ck](){return!0}[bn](e){return this.equals(e)}validate(e){return this.check(e)}check(e,t){pt()}get nullable(){return Qr(this,Rl)}get optional(){return new Sl(this)}cast(e){return HO(e,this),e}expect(e){return HO(e,this),e}};ol(Te,"_dilutes",!1);var Ms=class extends Te{constructor(e,t){super(),this.shape=e,this._c=t}check(e,t=void 0){let n=e?.constructor===this.shape&&(this._c==null||this._c(e));return!n&&t?.extend(null,this.shape.name,e?.constructor.name,e?.constructor!==this.shape?"Constructor match failed":"Check failed"),n}},Se=(i,e=null)=>new Ms(i,e),vR=Se(Ms),Rs=class extends Te{constructor(e){super(),this.shape=e}check(e,t){let n=this.shape(e);return!n&&t?.extend(null,"custom prop",e?.constructor.name,"failed to check custom prop"),n}},ve=i=>new Rs(i),$R=Se(Rs),wr=class extends Te{constructor(e){super(),this.shape=e}check(e,t){let n=this.shape.some(r=>r===e);return!n&&t?.extend(null,this.shape.join(" | "),e.toString()),n}},Al=(...i)=>new wr(i),KO=Se(wr),hk=RegExp.escape||(i=>i.replace(/[().|&,$^[\]]/g,e=>"\\"+e)),JO=i=>{if(kr.check(i))return[hk(i)];if(KO.check(i))return i.shape.map(e=>e+"");if(rg.check(i))return["[+-]?\\d+.?\\d*"];if(sg.check(i))return[".*"];if(Pl.check(i))return i.shape.map(JO).flat(1);Ce()},Zh=class extends Te{constructor(e){super(),this.shape=e,this._r=new RegExp("^"+e.map(JO).map(t=>`(${t.join("|")})`).join("")+"$")}check(e,t){let n=this._r.exec(e)!=null;return!n&&t?.extend(null,this._r.toString(),e.toString(),"String doesn't match string template."),n}};var PR=Se(Zh),fk=Symbol("optional"),Sl=class extends Te{constructor(e){super(),this.shape=e}check(e,t){let n=e===void 0||this.shape.check(e);return!n&&t?.extend(null,"undefined (optional)","()"),n}get[fk](){return!0}},uk=Se(Sl),xl=class extends Te{check(e,t){return t?.extend(null,"never",typeof e),!1}},CR=new xl,TR=Se(xl),Tl=class Tl extends Te{constructor(e,t=!1){super(),this.shape=e,this._isPartial=t}get partial(){return new Tl(this.shape,!0)}check(e,t){return e==null?(t?.extend(null,"object","null"),!1):br(this.shape,(n,r)=>{let s=this._isPartial&&!Ps(e,r)||n.check(e[r],t);return!s&&t?.extend(r.toString(),n.toString(),typeof e[r],"Object property does not match"),s})}};ol(Tl,"_dilutes",!0);var wl=Tl,dk=i=>new wl(i),pk=Se(wl),Ok=ve(i=>i!=null&&(i.constructor===Object||i.constructor==null)),kl=class extends Te{constructor(e,t){super(),this.shape={keys:e,values:t}}check(e,t){return e!=null&&br(e,(n,r)=>{let s=this.shape.keys.check(r,t);return!s&&t?.extend(r+"","Record",typeof e,s?"Key doesn't match schema":"Value doesn't match value"),s&&this.shape.values.check(n,t)})}},eg=(i,e)=>new kl(i,e),gk=Se(kl),Ql=class extends Te{constructor(e){super(),this.shape=e}check(e,t){return e!=null&&br(this.shape,(n,r)=>{let s=n.check(e[r],t);return!s&&t?.extend(r.toString(),"Tuple",typeof n),s})}},mk=(...i)=>new Ql(i),AR=Se(Ql),vl=class extends Te{constructor(e){super(),this.shape=e.length===1?e[0]:new Sr(e)}check(e,t){let n=ui(e)&&cl(e,r=>this.shape.check(r));return!n&&t?.extend(null,"Array",""),n}},tg=(...i)=>new vl(i),bk=Se(vl),yk=ve(i=>ui(i)),$l=class extends Te{constructor(e,t){super(),this.shape=e,this._c=t}check(e,t){let n=e instanceof this.shape&&(this._c==null||this._c(e));return!n&&t?.extend(null,this.shape.name,e?.constructor.name),n}},Sk=(i,e=null)=>new $l(i,e),MR=Se($l),xk=Sk(Te),_h=class extends Te{constructor(e){super(),this.len=e.length-1,this.args=mk(...e.slice(-1)),this.res=e[this.len]}check(e,t){let n=e.constructor===Function&&e.length<=this.len;return!n&&t?.extend(null,"function",typeof e),n}};var wk=Se(_h),kk=ve(i=>typeof i=="function"),Lh=class extends Te{constructor(e){super(),this.shape=e}check(e,t){let n=cl(this.shape,r=>r.check(e,t));return!n&&t?.extend(null,"Intersectinon",typeof e),n}};var RR=Se(Lh,i=>i.shape.length>0),Sr=class extends Te{constructor(e){super(),this.shape=e}check(e,t){let n=ys(this.shape,r=>r.check(e,t));return t?.extend(null,"Union",typeof e),n}};ol(Sr,"_dilutes",!0);var Qr=(...i)=>i.findIndex(e=>Pl.check(e))>=0?Qr(...i.map(e=>Xs(e)).map(e=>Pl.check(e)?e.shape:[e]).flat(1)):i.length===1?i[0]:new Sr(i),Pl=Se(Sr),ig=()=>!0,Cl=ve(ig),Qk=Se(Rs,i=>i.shape===ig),jh=ve(i=>typeof i=="bigint"),vk=ve(i=>i===jh),ng=ve(i=>typeof i=="symbol"),XR=ve(i=>i===ng),xr=ve(i=>typeof i=="number"),rg=ve(i=>i===xr),kr=ve(i=>typeof i=="string"),sg=ve(i=>i===kr),Ml=ve(i=>typeof i=="boolean"),$k=ve(i=>i===Ml),og=Al(void 0),ER=Se(wr,i=>i.shape.length===1&&i.shape[0]===void 0),ZR=Al(void 0);var Rl=Al(null),Pk=Se(wr,i=>i.shape.length===1&&i.shape[0]===null),_R=Se(Uint8Array),LR=Se(Ms,i=>i.shape===Uint8Array),Ck=Qr(xr,kr,Rl,og,jh,Ml,ng),DR=(()=>{let i=tg(Cl),e=eg(kr,Cl),t=Qr(xr,kr,Rl,Ml,i,e);return i.shape=t,e.shape.values=t,t})(),Xs=i=>{if(xk.check(i))return i;if(Ok.check(i)){let e={};for(let t in i)e[t]=Xs(i[t]);return dk(e)}else{if(yk.check(i))return Qr(...i.map(Xs));if(Ck.check(i))return Al(i);if(kk.check(i))return Se(i)}Ce()},HO=WO?()=>{}:(i,e)=>{let t=new Xh;if(!e.check(i,t))throw dt(`Expected value to be of type ${e.constructor.name}. +${t.toString()}`)},Dh=class{constructor(e){this.patterns=[],this.$state=e}if(e,t){return this.patterns.push({if:Xs(e),h:t}),this}else(e){return this.if(Cl,e)}done(){return(e,t)=>{for(let n=0;n<this.patterns.length;n++){let r=this.patterns[n];if(r.if.check(e))return r.h(e,t)}throw dt("Unhandled pattern")}}},Tk=i=>new Dh(i),lg=Tk(Cl).if(rg,(i,e)=>bl(e,lh,lr)).if(sg,(i,e)=>FO(e)).if($k,(i,e)=>Ah(e)).if(vk,(i,e)=>BigInt(bl(e,lh,lr))).if(Pl,(i,e)=>yr(e,yl(e,i.shape))).if(pk,(i,e)=>{let t={};for(let n in i.shape){let r=i.shape[n];if(uk.check(r)){if(Ah(e))continue;r=r.shape}t[n]=lg(r,e)}return t}).if(bk,(i,e)=>{let t=[],n=Mh(e,0,42);for(let r=0;r<n;r++)t.push(yr(e,i.shape));return t}).if(KO,(i,e)=>yl(e,i.shape)).if(Pk,(i,e)=>null).if(wk,(i,e)=>{let t=yr(e,i.res);return()=>t}).if(Qk,(i,e)=>yr(e,yl(e,[xr,kr,Rl,og,jh,Ml,tg(xr),eg(Qr("a","b","c"),xr)]))).if(gk,(i,e)=>{let t={},n=bl(e,0,3);for(let r=0;r<n;r++){let s=yr(e,i.shape.keys),o=yr(e,i.shape.values);t[s]=o}return t}).done(),yr=(i,e)=>lg(Xs(e),i);var ti=typeof document<"u"?document:{},Mk=i=>ti.createElement(i),Rk=()=>ti.createDocumentFragment(),IR=ve(i=>i.nodeType===jk),Xk=i=>ti.createTextNode(i),UR=typeof DOMParser<"u"?new DOMParser:null;var Ek=(i,e)=>(GO(e,(t,n)=>{n===!1?i.removeAttribute(t):n===!0?i.setAttribute(t,""):i.setAttribute(t,n)}),i);var Zk=i=>{let e=Rk();for(let t=0;t<i.length;t++)hg(e,i[t]);return e},ag=(i,e)=>(hg(i,Zk(e)),i);var Es=(i,e=[],t=[])=>ag(Ek(Mk(i),e),t),BR=ve(i=>i.nodeType===_k);var vr=Xk,VR=ve(i=>i.nodeType===Lk);var cg=i=>wO(i,(e,t)=>`${t}:${e};`).join("");var hg=(i,e)=>i.appendChild(e),_k=ti.ELEMENT_NODE,Lk=ti.TEXT_NODE,YR=ti.CDATA_SECTION_NODE,zR=ti.COMMENT_NODE,Dk=ti.DOCUMENT_NODE,WR=ti.DOCUMENT_TYPE_NODE,jk=ti.DOCUMENT_FRAGMENT_NODE,NR=ve(i=>i.nodeType===Dk);var ii=Symbol;var Zs=ii(),_s=ii(),Ih=ii(),Uh=ii(),Bh=ii(),Ls=ii(),Vh=ii(),$r=ii(),Yh=ii(),ug=i=>{i.length===1&&i[0]?.constructor===Function&&(i=i[0]());let e=[],t=[],n=0;for(;n<i.length;n++){let r=i[n];if(r===void 0)break;if(r.constructor===String||r.constructor===Number)e.push(r);else if(r.constructor===Object)break}for(n>0&&t.push(e.join(""));n<i.length;n++){let r=i[n];r instanceof Symbol||t.push(r)}return t};var qR=pi();var Bk={[Zs]:Ge("font-weight","bold"),[_s]:Ge("font-weight","normal"),[Ih]:Ge("color","blue"),[Bh]:Ge("color","green"),[Uh]:Ge("color","grey"),[Ls]:Ge("color","red"),[Vh]:Ge("color","purple"),[$r]:Ge("color","orange"),[Yh]:Ge("color","black")},Vk=i=>{i.length===1&&i[0]?.constructor===Function&&(i=i[0]());let e=[],t=[],n=pe(),r=[],s=0;for(;s<i.length;s++){let o=i[s],l=Bk[o];if(l!==void 0)n.set(l.left,l.right);else{if(o===void 0)break;if(o.constructor===String||o.constructor===Number){let a=cg(n);s>0||a.length>0?(e.push("%c"+o),t.push(a)):e.push(o)}else break}}for(s>0&&(r=t,r.unshift(e.join("")));s<i.length;s++){let o=i[s];o instanceof Symbol||r.push(o)}return r},dg=NO?Vk:ug,Xl=(...i)=>{console.log(...dg(i)),pg.forEach(e=>e.print(i))},zh=(...i)=>{console.warn(...dg(i)),i.unshift($r),pg.forEach(e=>e.print(i))};var pg=Ht();var Og=i=>({[Symbol.iterator](){return this},next:i}),gg=(i,e)=>Og(()=>{let t;do t=i.next();while(!t.done&&!e(t.value));return t}),El=(i,e)=>Og(()=>{let{done:t,value:n}=i.next();return{done:t,value:t?void 0:e(n)}});var Nh=class extends sr{constructor(e,t){super(),this.doc=e,this.awareness=t}},Pr=class{constructor(e,t){this.clock=e,this.len=t}},Zi=class{constructor(){this.clients=new Map}},xn=(i,e,t)=>e.clients.forEach((n,r)=>{let s=i.doc.store.clients.get(r);if(s!=null){let o=s[s.length-1],l=o.id.clock+o.length;for(let a=0,c=n[a];a<n.length&&c.clock<l;c=n[++a])Bg(i,s,c.clock,c.len,t)}}),qk=(i,e)=>{let t=0,n=i.length-1;for(;t<=n;){let r=Re((t+n)/2),s=i[r],o=s.clock;if(o<=e){if(e<o+s.len)return r;t=r+1}else n=r-1}return null},Mn=(i,e)=>{let t=i.clients.get(e.client);return t!==void 0&&qk(t,e.clock)!==null},rf=i=>{i.clients.forEach(e=>{e.sort((r,s)=>r.clock-s.clock);let t,n;for(t=1,n=1;t<e.length;t++){let r=e[n-1],s=e[t];r.clock+r.len>=s.clock?e[n-1]=new Pr(r.clock,Lt(r.len,s.clock+s.len-r.clock)):(n<t&&(e[n]=s),n++)}e.length=n})},wn=i=>{let e=new Zi;for(let t=0;t<i.length;t++)i[t].clients.forEach((n,r)=>{if(!e.clients.has(r)){let s=n.slice();for(let o=t+1;o<i.length;o++)QO(s,i[o].clients.get(r)||[]);e.clients.set(r,s)}});return rf(e),e},js=(i,e,t,n)=>{Ie(i.clients,e,()=>[]).push(new Pr(t,n))},Kl=()=>new Zi,sf=i=>{let e=Kl();return i.clients.forEach((t,n)=>{let r=[];for(let s=0;s<t.length;s++){let o=t[s];if(o.deleted){let l=o.id.clock,a=o.length;if(s+1<t.length)for(let c=t[s+1];s+1<t.length&&c.deleted;c=t[++s+1])a+=c.length;r.push(new Pr(l,a))}}r.length>0&&e.clients.set(n,r)}),e},ni=(i,e)=>{B(i.restEncoder,e.clients.size),_t(e.clients.entries()).sort((t,n)=>n[0]-t[0]).forEach(([t,n])=>{i.resetDsCurVal(),B(i.restEncoder,t);let r=n.length;B(i.restEncoder,r);for(let s=0;s<r;s++){let o=n[s];i.writeDsClock(o.clock),i.writeDsLen(o.len)}})},gi=i=>{let e=new Zi,t=z(i.restDecoder);for(let n=0;n<t;n++){i.resetDsCurVal();let r=z(i.restDecoder),s=z(i.restDecoder);if(s>0){let o=Ie(e.clients,r,()=>[]);for(let l=0;l<s;l++)o.push(new Pr(i.readDsClock(),i.readDsLen()))}}return e},yg=(i,e,t)=>{let n=new Zi,r=z(i.restDecoder);for(let s=0;s<r;s++){i.resetDsCurVal();let o=z(i.restDecoder),l=z(i.restDecoder),a=t.clients.get(o)||[],c=ue(t,o);for(let h=0;h<l;h++){let f=i.readDsClock(),u=f+i.readDsLen();if(f<c){c<u&&js(n,o,c,u-c);let d=$t(a,f),p=a[d];for(!p.deleted&&p.id.clock<f&&(a.splice(d+1,0,Hl(e,p,f-p.id.clock)),d++);d<a.length&&(p=a[d++],p.id.clock<u);)p.deleted||(u<p.id.clock+p.length&&a.splice(d,0,Hl(e,p,u-p.id.clock)),p.delete(e))}else js(n,o,f,u-f)}}if(n.clients.size>0){let s=new Ot;return B(s.restEncoder,0),ni(s,n),s.toUint8Array()}return null},Eg=(i,e)=>{if(i.clients.size!==e.clients.size)return!1;for(let[t,n]of i.clients.entries()){let r=e.clients.get(t);if(r===void 0||n.length!==r.length)return!1;for(let s=0;s<n.length;s++){let o=n[s],l=r[s];if(o.clock!==l.clock||o.len!==l.len)return!1}}return!0},Zg=gh,_i=class i extends sr{constructor({guid:e=LO(),collectionid:t=null,gc:n=!0,gcFilter:r=()=>!0,meta:s=null,autoLoad:o=!1,shouldLoad:l=!0}={}){super(),this.gc=n,this.gcFilter=r,this.clientID=Zg(),this.guid=e,this.collectionid=t,this.share=new Map,this.store=new Bl,this._transaction=null,this._transactionCleanups=[],this.subdocs=new Set,this._item=null,this.shouldLoad=l,this.autoLoad=o,this.meta=s,this.isLoaded=!1,this.isSynced=!1,this.isDestroyed=!1,this.whenLoaded=bh(c=>{this.on("load",()=>{this.isLoaded=!0,c(this)})});let a=()=>bh(c=>{let h=f=>{(f===void 0||f===!0)&&(this.off("sync",h),c())};this.on("sync",h)});this.on("sync",c=>{c===!1&&this.isSynced&&(this.whenSynced=a()),this.isSynced=c===void 0||c===!0,this.isSynced&&!this.isLoaded&&this.emit("load",[this])}),this.whenSynced=a()}load(){let e=this._item;e!==null&&!this.shouldLoad&&oe(e.parent.doc,t=>{t.subdocsLoaded.add(this)},null,!0),this.shouldLoad=!0}getSubdocs(){return this.subdocs}getSubdocGuids(){return new Set(_t(this.subdocs).map(e=>e.guid))}transact(e,t=null){return oe(this,e,t)}get(e,t=ge){let n=Ie(this.share,e,()=>{let s=new t;return s._integrate(this,null),s}),r=n.constructor;if(t!==ge&&r!==t)if(r===ge){let s=new t;s._map=n._map,n._map.forEach(o=>{for(;o!==null;o=o.left)o.parent=s}),s._start=n._start;for(let o=s._start;o!==null;o=o.right)o.parent=s;return s._length=n._length,this.share.set(e,s),s._integrate(this,null),s}else throw new Error(`Type with the name ${e} has already been defined with a different constructor`);return n}getArray(e=""){return this.get(e,vn)}getText(e=""){return this.get(e,Ar)}getMap(e=""){return this.get(e,$n)}getXmlElement(e=""){return this.get(e,Cn)}getXmlFragment(e=""){return this.get(e,Pn)}toJSON(){let e={};return this.share.forEach((t,n)=>{e[n]=t.toJSON()}),e}destroy(){this.isDestroyed=!0,_t(this.subdocs).forEach(t=>t.destroy());let e=this._item;if(e!==null){this._item=null;let t=e.content;t.doc=new i({guid:this.guid,...t.opts,shouldLoad:!1}),t.doc._item=e,oe(e.parent.doc,n=>{let r=t.doc;e.deleted||n.subdocsAdded.add(r),n.subdocsRemoved.add(this)},null,!0)}this.emit("destroyed",[!0]),this.emit("destroy",[this]),super.destroy()}},kn=class{constructor(e){this.restDecoder=e}resetDsCurVal(){}readDsClock(){return z(this.restDecoder)}readDsLen(){return z(this.restDecoder)}},ot=class extends kn{readLeftID(){return W(z(this.restDecoder),z(this.restDecoder))}readRightID(){return W(z(this.restDecoder),z(this.restDecoder))}readClient(){return z(this.restDecoder)}readInfo(){return gn(this.restDecoder)}readString(){return Jt(this.restDecoder)}readParentInfo(){return z(this.restDecoder)===1}readTypeRef(){return z(this.restDecoder)}readLen(){return z(this.restDecoder)}readAny(){return gr(this.restDecoder)}readBuf(){return qO(st(this.restDecoder))}readJSON(){return JSON.parse(Jt(this.restDecoder))}readKey(){return Jt(this.restDecoder)}},Il=class{constructor(e){this.dsCurrVal=0,this.restDecoder=e}resetDsCurVal(){this.dsCurrVal=0}readDsClock(){return this.dsCurrVal+=z(this.restDecoder),this.dsCurrVal}readDsLen(){let e=z(this.restDecoder)+1;return this.dsCurrVal+=e,e}},Fe=class extends Il{constructor(e){super(e),this.keys=[],z(e),this.keyClockDecoder=new mr(st(e)),this.clientDecoder=new mn(st(e)),this.leftClockDecoder=new mr(st(e)),this.rightClockDecoder=new mr(st(e)),this.infoDecoder=new Qs(st(e),gn),this.stringDecoder=new pl(st(e)),this.parentInfoDecoder=new Qs(st(e),gn),this.typeRefDecoder=new mn(st(e)),this.lenDecoder=new mn(st(e))}readLeftID(){return new Oi(this.clientDecoder.read(),this.leftClockDecoder.read())}readRightID(){return new Oi(this.clientDecoder.read(),this.rightClockDecoder.read())}readClient(){return this.clientDecoder.read()}readInfo(){return this.infoDecoder.read()}readString(){return this.stringDecoder.read()}readParentInfo(){return this.parentInfoDecoder.read()===1}readTypeRef(){return this.typeRefDecoder.read()}readLen(){return this.lenDecoder.read()}readAny(){return gr(this.restDecoder)}readBuf(){return st(this.restDecoder)}readJSON(){return gr(this.restDecoder)}readKey(){let e=this.keyClockDecoder.read();if(e<this.keys.length)return this.keys[e];{let t=this.stringDecoder.read();return this.keys.push(t),t}}},Li=class{constructor(){this.restEncoder=di()}toUint8Array(){return qe(this.restEncoder)}resetDsCurVal(){}writeDsClock(e){B(this.restEncoder,e)}writeDsLen(e){B(this.restEncoder,e)}},ri=class extends Li{writeLeftID(e){B(this.restEncoder,e.client),B(this.restEncoder,e.clock)}writeRightID(e){B(this.restEncoder,e.client),B(this.restEncoder,e.clock)}writeClient(e){B(this.restEncoder,e)}writeInfo(e){dr(this.restEncoder,e)}writeString(e){Kt(this.restEncoder,e)}writeParentInfo(e){B(this.restEncoder,e?1:0)}writeTypeRef(e){B(this.restEncoder,e)}writeLen(e){B(this.restEncoder,e)}writeAny(e){fr(this.restEncoder,e)}writeBuf(e){rt(this.restEncoder,e)}writeJSON(e){Kt(this.restEncoder,JSON.stringify(e))}writeKey(e){Kt(this.restEncoder,e)}},Cr=class{constructor(){this.restEncoder=di(),this.dsCurrVal=0}toUint8Array(){return qe(this.restEncoder)}resetDsCurVal(){this.dsCurrVal=0}writeDsClock(e){let t=e-this.dsCurrVal;this.dsCurrVal=e,B(this.restEncoder,t)}writeDsLen(e){e===0&&Ce(),B(this.restEncoder,e-1),this.dsCurrVal+=e}},Ot=class extends Cr{constructor(){super(),this.keyMap=new Map,this.keyClock=0,this.keyClockEncoder=new ur,this.clientEncoder=new On,this.leftClockEncoder=new ur,this.rightClockEncoder=new ur,this.infoEncoder=new ws(dr),this.stringEncoder=new dl,this.parentInfoEncoder=new ws(dr),this.typeRefEncoder=new On,this.lenEncoder=new On}toUint8Array(){let e=di();return B(e,0),rt(e,this.keyClockEncoder.toUint8Array()),rt(e,this.clientEncoder.toUint8Array()),rt(e,this.leftClockEncoder.toUint8Array()),rt(e,this.rightClockEncoder.toUint8Array()),rt(e,qe(this.infoEncoder)),rt(e,this.stringEncoder.toUint8Array()),rt(e,qe(this.parentInfoEncoder)),rt(e,this.typeRefEncoder.toUint8Array()),rt(e,this.lenEncoder.toUint8Array()),pr(e,qe(this.restEncoder)),qe(e)}writeLeftID(e){this.clientEncoder.write(e.client),this.leftClockEncoder.write(e.clock)}writeRightID(e){this.clientEncoder.write(e.client),this.rightClockEncoder.write(e.clock)}writeClient(e){this.clientEncoder.write(e)}writeInfo(e){this.infoEncoder.write(e)}writeString(e){this.stringEncoder.write(e)}writeParentInfo(e){this.parentInfoEncoder.write(e?1:0)}writeTypeRef(e){this.typeRefEncoder.write(e)}writeLen(e){this.lenEncoder.write(e)}writeAny(e){fr(this.restEncoder,e)}writeBuf(e){rt(this.restEncoder,e)}writeJSON(e){fr(this.restEncoder,e)}writeKey(e){let t=this.keyMap.get(e);t===void 0?(this.keyClockEncoder.write(this.keyClock++),this.stringEncoder.write(e)):this.keyClockEncoder.write(t)}},Gk=(i,e,t,n)=>{n=Lt(n,e[0].id.clock);let r=$t(e,n);B(i.restEncoder,e.length-r),i.writeClient(t),B(i.restEncoder,n);let s=e[r];s.write(i,n-s.id.clock);for(let o=r+1;o<e.length;o++)e[o].write(i,0)},of=(i,e,t)=>{let n=new Map;t.forEach((r,s)=>{ue(e,s)>r&&n.set(s,r)}),Fs(e).forEach((r,s)=>{t.has(s)||n.set(s,0)}),B(i.restEncoder,n.size),_t(n.entries()).sort((r,s)=>s[0]-r[0]).forEach(([r,s])=>{Gk(i,e.clients.get(r),r,s)})},Fk=(i,e)=>{let t=pe(),n=z(i.restDecoder);for(let r=0;r<n;r++){let s=z(i.restDecoder),o=new Array(s),l=i.readClient(),a=z(i.restDecoder);t.set(l,{i:0,refs:o});for(let c=0;c<s;c++){let h=i.readInfo();switch(31&h){case 0:{let f=i.readLen();o[c]=new Be(W(l,a),f),a+=f;break}case 10:{let f=z(i.restDecoder);o[c]=new Xe(W(l,a),f),a+=f;break}default:{let f=(h&192)===0,u=new le(W(l,a),null,(h&128)===128?i.readLeftID():null,null,(h&64)===64?i.readRightID():null,f?i.readParentInfo()?e.get(i.readString()):i.readLeftID():null,f&&(h&32)===32?i.readString():null,ym(i,h));o[c]=u,a+=u.length}}}}return t},Hk=(i,e,t)=>{let n=[],r=_t(t.keys()).sort((d,p)=>d-p);if(r.length===0)return null;let s=()=>{if(r.length===0)return null;let d=t.get(r[r.length-1]);for(;d.refs.length===d.i;)if(r.pop(),r.length>0)d=t.get(r[r.length-1]);else return null;return d},o=s();if(o===null)return null;let l=new Bl,a=new Map,c=(d,p)=>{let O=a.get(d);(O==null||O>p)&&a.set(d,p)},h=o.refs[o.i++],f=new Map,u=()=>{for(let d of n){let p=d.id.client,O=t.get(p);O?(O.i--,l.clients.set(p,O.refs.slice(O.i)),t.delete(p),O.i=0,O.refs=[]):l.clients.set(p,[d]),r=r.filter(g=>g!==p)}n.length=0};for(;;){if(h.constructor!==Xe){let p=Ie(f,h.id.client,()=>ue(e,h.id.client))-h.id.clock;if(p<0)n.push(h),c(h.id.client,h.id.clock-1),u();else{let O=h.getMissing(i,e);if(O!==null){n.push(h);let g=t.get(O)||{refs:[],i:0};if(g.refs.length===g.i)c(O,ue(e,O)),u();else{h=g.refs[g.i++];continue}}else(p===0||p<h.length)&&(h.integrate(i,p),f.set(h.id.client,h.id.clock+h.length))}}if(n.length>0)h=n.pop();else if(o!==null&&o.i<o.refs.length)h=o.refs[o.i++];else{if(o=s(),o===null)break;h=o.refs[o.i++]}}if(l.clients.size>0){let d=new Ot;return of(d,l,new Map),B(d.restEncoder,0),{missing:a,update:d.toUint8Array()}}return null},Kk=(i,e)=>of(i,e.doc.store,e.beforeState),lf=(i,e,t,n=new Fe(i))=>oe(e,r=>{r.local=!1;let s=!1,o=r.doc,l=o.store,a=Fk(n,o),c=Hk(r,l,a),h=l.pendingStructs;if(h){for(let[u,d]of h.missing)if(d<ue(l,u)){s=!0;break}if(c){for(let[u,d]of c.missing){let p=h.missing.get(u);(p==null||p>d)&&h.missing.set(u,d)}h.update=Ys([h.update,c.update])}}else l.pendingStructs=c;let f=yg(n,r,l);if(l.pendingDs){let u=new Fe(Qe(l.pendingDs));z(u.restDecoder);let d=yg(u,r,l);f&&d?l.pendingDs=Ys([f,d]):l.pendingDs=f||d}else l.pendingDs=f;if(s){let u=l.pendingStructs.update;l.pendingStructs=null,Jl(r.doc,u)}},t,!1),Jk=(i,e,t)=>lf(i,e,t,new ot(i)),Jl=(i,e,t,n=Fe)=>{let r=Qe(e);lf(r,i,t,new n(r))},eQ=(i,e,t)=>Jl(i,e,t,ot),tQ=(i,e,t=new Map)=>{of(i,e.store,t),ni(i,sf(e.store))},_g=(i,e=new Uint8Array([0]),t=new Ot)=>{let n=af(e);tQ(t,i,n);let r=[t.toUint8Array()];if(i.store.pendingDs&&r.push(i.store.pendingDs),i.store.pendingStructs&&r.push(uf(i.store.pendingStructs.update,e)),r.length>1){if(t.constructor===ri)return qg(r.map((s,o)=>o===0?s:Jg(s)));if(t.constructor===Ot)return Ys(r)}return r[0]},iQ=(i,e)=>_g(i,e,new ri),Lg=i=>{let e=new Map,t=z(i.restDecoder);for(let n=0;n<t;n++){let r=z(i.restDecoder),s=z(i.restDecoder);e.set(r,s)}return e},af=i=>Lg(new kn(Qe(i))),cf=(i,e)=>(B(i.restEncoder,e.size),_t(e.entries()).sort((t,n)=>n[0]-t[0]).forEach(([t,n])=>{B(i.restEncoder,t),B(i.restEncoder,n)}),i),nQ=(i,e)=>cf(i,Fs(e.store)),rQ=(i,e=new Cr)=>(i instanceof Map?cf(e,i):nQ(e,i),e.toUint8Array()),sQ=i=>rQ(i,new Li),qh=class{constructor(){this.l=[]}},Sg=()=>new qh,xg=(i,e)=>i.l.push(e),wg=(i,e)=>{let t=i.l,n=t.length;i.l=t.filter(r=>e!==r),n===i.l.length&&console.error("[yjs] Tried to remove event handler that doesn't exist.")},Dg=(i,e,t)=>Cs(i.l,[e,t]),Oi=class{constructor(e,t){this.client=e,this.clock=t}},yn=(i,e)=>i===e||i!==null&&e!==null&&i.client===e.client&&i.clock===e.clock,W=(i,e)=>new Oi(i,e),kg=(i,e)=>{B(i,e.client),B(i,e.clock)},Qg=i=>W(z(i),z(i)),hf=i=>{for(let[e,t]of i.doc.share.entries())if(t===i)return e;throw Ce()},Is=(i,e)=>{for(;e!==null;){if(e.parent===i)return!0;e=e.parent._item}return!1},oQ=i=>{let e=[],t=i._start;for(;t;)e.push(t),t=t.right;console.log("Children: ",e),console.log("Children content: ",e.filter(n=>!n.deleted).map(n=>n.content))},Gh=class{constructor(e,t=e.getMap("users")){let n=new Map;this.yusers=t,this.doc=e,this.clients=new Map,this.dss=n;let r=(s,o)=>{let l=s.get("ds"),a=s.get("ids"),c=h=>this.clients.set(h,o);l.observe(h=>{h.changes.added.forEach(f=>{f.content.getContent().forEach(u=>{u instanceof Uint8Array&&this.dss.set(o,wn([this.dss.get(o)||Kl(),gi(new kn(Qe(u)))]))})})}),this.dss.set(o,wn(l.map(h=>gi(new kn(Qe(h)))))),a.observe(h=>h.changes.added.forEach(f=>f.content.getContent().forEach(c))),a.forEach(c)};t.observe(s=>{s.keysChanged.forEach(o=>r(t.get(o),o))}),t.forEach(r)}setUserMapping(e,t,n,{filter:r=()=>!0}={}){let s=this.yusers,o=s.get(n);o||(o=new $n,o.set("ids",new vn),o.set("ds",new vn),s.set(n,o)),o.get("ids").push([t]),s.observe(l=>{setTimeout(()=>{let a=s.get(n);if(a!==o){o=a,this.clients.forEach((f,u)=>{n===f&&o.get("ids").push([u])});let c=new Li,h=this.dss.get(n);h&&(ni(c,h),o.get("ds").push([c.toUint8Array()]))}},0)}),e.on("afterTransaction",l=>{setTimeout(()=>{let a=o.get("ds"),c=l.deleteSet;if(l.local&&c.clients.size>0&&r(l,c)){let h=new Li;ni(h,c),a.push([h.toUint8Array()])}})})}getUserByClientId(e){return this.clients.get(e)||null}getUserByDeletedId(e){for(let[t,n]of this.dss.entries())if(Mn(n,e))return t;return null}},Tr=class{constructor(e,t,n,r=0){this.type=e,this.tname=t,this.item=n,this.assoc=r}},ea=i=>{let e={};return i.type&&(e.type=i.type),i.tname&&(e.tname=i.tname),i.item&&(e.item=i.item),i.assoc!=null&&(e.assoc=i.assoc),e},bi=i=>new Tr(i.type==null?null:W(i.type.client,i.type.clock),i.tname??null,i.item==null?null:W(i.item.client,i.item.clock),i.assoc==null?0:i.assoc),Ul=class{constructor(e,t,n=0){this.type=e,this.index=t,this.assoc=n}},lQ=(i,e,t=0)=>new Ul(i,e,t),Zl=(i,e,t)=>{let n=null,r=null;return i._item===null?r=hf(i):n=W(i._item.id.client,i._item.id.clock),new Tr(n,r,e,t)},Xr=(i,e,t=0)=>{let n=i._start;if(t<0){if(e===0)return Zl(i,null,t);e--}for(;n!==null;){if(!n.deleted&&n.countable){if(n.length>e)return Zl(i,W(n.id.client,n.id.clock+e),t);e-=n.length}if(n.right===null&&t<0)return Zl(i,n.lastId,t);n=n.right}return Zl(i,null,t)},aQ=(i,e)=>{let{type:t,tname:n,item:r,assoc:s}=e;if(r!==null)B(i,0),kg(i,r);else if(n!==null)dr(i,1),Kt(i,n);else if(t!==null)dr(i,2),kg(i,t);else throw Ce();return ks(i,s),i},cQ=i=>{let e=di();return aQ(e,i),qe(e)},hQ=i=>{let e=null,t=null,n=null;switch(z(i)){case 0:n=Qg(i);break;case 1:t=Jt(i);break;case 2:e=Qg(i)}let r=ph(i)?vs(i):0;return new Tr(e,t,n,r)},fQ=i=>hQ(Qe(i)),uQ=(i,e)=>{let t=Sn(i,e),n=e.clock-t.id.clock;return{item:t,diff:n}},Er=(i,e,t=!0)=>{let n=e.store,r=i.item,s=i.type,o=i.tname,l=i.assoc,a=null,c=0;if(r!==null){if(ue(n,r.client)<=r.clock)return null;let h=t?tf(n,r):uQ(n,r),f=h.item;if(!(f instanceof le))return null;if(a=f.parent,a._item===null||!a._item.deleted){c=f.deleted||!f.countable?0:h.diff+(l>=0?0:1);let u=f.left;for(;u!==null;)!u.deleted&&u.countable&&(c+=u.length),u=u.left}}else{if(o!==null)a=e.get(o);else if(s!==null){if(ue(n,s.client)<=s.clock)return null;let{item:h}=t?tf(n,s):{item:Sn(n,s)};if(h instanceof le&&h.content instanceof Pt)a=h.content.type;else return null}else throw Ce();l>=0?c=a._length:c=0}return lQ(a,c,i.assoc)},ta=(i,e)=>i===e||i!==null&&e!==null&&i.tname===e.tname&&yn(i.item,e.item)&&yn(i.type,e.type)&&i.assoc===e.assoc,Us=class{constructor(e,t){this.ds=e,this.sv=t}},dQ=(i,e)=>{let t=i.ds.clients,n=e.ds.clients,r=i.sv,s=e.sv;if(r.size!==s.size||t.size!==n.size)return!1;for(let[o,l]of r.entries())if(s.get(o)!==l)return!1;for(let[o,l]of t.entries()){let a=n.get(o)||[];if(l.length!==a.length)return!1;for(let c=0;c<l.length;c++){let h=l[c],f=a[c];if(h.clock!==f.clock||h.len!==f.len)return!1}}return!0},jg=(i,e=new Cr)=>(ni(e,i.ds),cf(e,i.sv),e.toUint8Array()),pQ=i=>jg(i,new Li),Ig=(i,e=new Il(Qe(i)))=>new Us(gi(e),Lg(e)),OQ=i=>Ig(i,new kn(Qe(i))),ff=(i,e)=>new Us(i,e),gQ=ff(Kl(),new Map),mQ=i=>ff(sf(i.store),Fs(i.store)),Ri=(i,e)=>e===void 0?!i.deleted:e.sv.has(i.id.client)&&(e.sv.get(i.id.client)||0)>i.id.clock&&!Mn(e.ds,i.id),Fh=(i,e)=>{let t=Ie(i.meta,Fh,Ht),n=i.doc.store;t.has(e)||(e.sv.forEach((r,s)=>{r<ue(n,s)&&Ue(i,W(s,r))}),xn(i,e.ds,r=>{}),t.add(e))},bQ=(i,e,t=new _i)=>{if(i.gc)throw new Error("Garbage-collection must be disabled in `originDoc`!");let{sv:n,ds:r}=e,s=new Ot;return i.transact(o=>{let l=0;n.forEach(a=>{a>0&&l++}),B(s.restEncoder,l);for(let[a,c]of n){if(c===0)continue;c<ue(i.store,a)&&Ue(o,W(a,c));let h=i.store.clients.get(a)||[],f=$t(h,c-1);B(s.restEncoder,f+1),s.writeClient(a),B(s.restEncoder,0);for(let u=0;u<=f;u++)h[u].write(s,0)}ni(s,r)}),Jl(t,s.toUint8Array(),"snapshot"),t},yQ=(i,e,t=Fe)=>{let n=new t(Qe(e)),r=new si(n,!1);for(let o=r.curr;o!==null;o=r.next())if((i.sv.get(o.id.client)||0)<o.id.clock+o.length)return!1;let s=wn([i.ds,gi(n)]);return Eg(i.ds,s)},SQ=(i,e)=>yQ(i,e,ot),Bl=class{constructor(){this.clients=new Map,this.pendingStructs=null,this.pendingDs=null}},Fs=i=>{let e=new Map;return i.clients.forEach((t,n)=>{let r=t[t.length-1];e.set(n,r.id.clock+r.length)}),e},ue=(i,e)=>{let t=i.clients.get(e);if(t===void 0)return 0;let n=t[t.length-1];return n.id.clock+n.length},Ug=(i,e)=>{let t=i.clients.get(e.id.client);if(t===void 0)t=[],i.clients.set(e.id.client,t);else{let n=t[t.length-1];if(n.id.clock+n.length!==e.id.clock)throw Ce()}t.push(e)},$t=(i,e)=>{let t=0,n=i.length-1,r=i[n],s=r.id.clock;if(s===e)return n;let o=Re(e/(s+r.length-1)*n);for(;t<=n;){if(r=i[o],s=r.id.clock,s<=e){if(e<s+r.length)return o;t=o+1}else n=o-1;o=Re((t+n)/2)}throw Ce()},xQ=(i,e)=>{let t=i.clients.get(e.client);return t[$t(t,e.clock)]},Sn=xQ,Hh=(i,e,t)=>{let n=$t(e,t),r=e[n];return r.id.clock<t&&r instanceof le?(e.splice(n+1,0,Hl(i,r,t-r.id.clock)),n+1):n},Ue=(i,e)=>{let t=i.doc.store.clients.get(e.client);return t[Hh(i,t,e.clock)]},Kh=(i,e,t)=>{let n=e.clients.get(t.client),r=$t(n,t.clock),s=n[r];return t.clock!==s.id.clock+s.length-1&&s.constructor!==Be&&n.splice(r+1,0,Hl(i,s,t.clock-s.id.clock+1)),s},wQ=(i,e,t)=>{let n=i.clients.get(e.id.client);n[$t(n,e.id.clock)]=t},Bg=(i,e,t,n,r)=>{if(n===0)return;let s=t+n,o=Hh(i,e,t),l;do l=e[o++],s<l.id.clock+l.length&&Hh(i,e,s),r(l);while(o<e.length&&e[o].id.clock<s)},Vl=class{constructor(e,t,n){this.doc=e,this.deleteSet=new Zi,this.beforeState=Fs(e.store),this.afterState=new Map,this.changed=new Map,this.changedParentTypes=new Map,this._mergeStructs=[],this.origin=t,this.meta=new Map,this.local=n,this.subdocsAdded=new Set,this.subdocsRemoved=new Set,this.subdocsLoaded=new Set,this._needFormattingCleanup=!1}},vg=(i,e)=>e.deleteSet.clients.size===0&&!kO(e.afterState,(t,n)=>e.beforeState.get(n)!==t)?!1:(rf(e.deleteSet),Kk(i,e),ni(i,e.deleteSet),!0),$g=(i,e,t)=>{let n=e._item;(n===null||n.id.clock<(i.beforeState.get(n.id.client)||0)&&!n.deleted)&&Ie(i.changed,e,Ht).add(t)},Dl=(i,e)=>{let t=i[e],n=i[e-1],r=e;for(;r>0;t=n,n=i[--r-1]){if(n.deleted===t.deleted&&n.constructor===t.constructor&&n.mergeWith(t)){t instanceof le&&t.parentSub!==null&&t.parent._map.get(t.parentSub)===t&&t.parent._map.set(t.parentSub,n);continue}break}let s=e-r;return s&&i.splice(e+1-s,s),s},Vg=(i,e,t)=>{for(let[n,r]of i.clients.entries()){let s=e.clients.get(n);for(let o=r.length-1;o>=0;o--){let l=r[o],a=l.clock+l.len;for(let c=$t(s,l.clock),h=s[c];c<s.length&&h.id.clock<a;h=s[++c]){let f=s[c];if(l.clock+l.len<=f.id.clock)break;f instanceof le&&f.deleted&&!f.keep&&t(f)&&f.gc(e,!1)}}}},Yg=(i,e)=>{i.clients.forEach((t,n)=>{let r=e.clients.get(n);for(let s=t.length-1;s>=0;s--){let o=t[s],l=or(r.length-1,1+$t(r,o.clock+o.len-1));for(let a=l,c=r[a];a>0&&c.id.clock>=o.clock;c=r[a])a-=1+Dl(r,a)}})},kQ=(i,e,t)=>{Vg(i,e,t),Yg(i,e)},zg=(i,e)=>{if(e<i.length){let t=i[e],n=t.doc,r=n.store,s=t.deleteSet,o=t._mergeStructs;try{rf(s),t.afterState=Fs(t.doc.store),n.emit("beforeObserverCalls",[t,n]);let l=[];t.changed.forEach((a,c)=>l.push(()=>{(c._item===null||!c._item.deleted)&&c._callObserver(t,a)})),l.push(()=>{t.changedParentTypes.forEach((a,c)=>{c._dEH.l.length>0&&(c._item===null||!c._item.deleted)&&l.push(()=>{a=a.filter(h=>h.target._item===null||!h.target._item.deleted),a.forEach(h=>{h.currentTarget=c,h._path=null}),a.sort((h,f)=>h.path.length-f.path.length),Dg(c._dEH,a,t)})}),l.push(()=>n.emit("afterTransaction",[t,n])),l.push(()=>{t._needFormattingCleanup&&zQ(t)})}),Cs(l,[])}finally{n.gc&&Vg(s,r,n.gcFilter),Yg(s,r),t.afterState.forEach((h,f)=>{let u=t.beforeState.get(f)||0;if(u!==h){let d=r.clients.get(f),p=Lt($t(d,u),1);for(let O=d.length-1;O>=p;)O-=1+Dl(d,O)}});for(let h=o.length-1;h>=0;h--){let{client:f,clock:u}=o[h].id,d=r.clients.get(f),p=$t(d,u);p+1<d.length&&Dl(d,p+1)>1||p>0&&Dl(d,p)}if(!t.local&&t.afterState.get(n.clientID)!==t.beforeState.get(n.clientID)&&(Xl($r,Zs,"[yjs] ",_s,Ls,"Changed the client-id because another client seems to be using it."),n.clientID=Zg()),n.emit("afterTransactionCleanup",[t,n]),n._observers.has("update")){let h=new ri;vg(h,t)&&n.emit("update",[h.toUint8Array(),t.origin,n,t])}if(n._observers.has("updateV2")){let h=new Ot;vg(h,t)&&n.emit("updateV2",[h.toUint8Array(),t.origin,n,t])}let{subdocsAdded:l,subdocsLoaded:a,subdocsRemoved:c}=t;(l.size>0||c.size>0||a.size>0)&&(l.forEach(h=>{h.clientID=n.clientID,h.collectionid==null&&(h.collectionid=n.collectionid),n.subdocs.add(h)}),c.forEach(h=>n.subdocs.delete(h)),n.emit("subdocs",[{loaded:a,added:l,removed:c},n,t]),c.forEach(h=>h.destroy())),i.length<=e+1?(n._transactionCleanups=[],n.emit("afterAllTransactions",[n,i])):zg(i,e+1)}}},oe=(i,e,t=null,n=!0)=>{let r=i._transactionCleanups,s=!1,o=null;i._transaction===null&&(s=!0,i._transaction=new Vl(i,t,n),r.push(i._transaction),r.length===1&&i.emit("beforeAllTransactions",[i]),i.emit("beforeTransaction",[i._transaction,i]));try{o=e(i._transaction)}finally{if(s){let l=i._transaction===r[0];i._transaction=null,l&&zg(r,0)}}return o},Jh=class{constructor(e,t){this.insertions=t,this.deletions=e,this.meta=new Map}},Pg=(i,e,t)=>{xn(i,t.deletions,n=>{n instanceof le&&e.scope.some(r=>r===i.doc||Is(r,n))&&mf(n,!1)})},Cg=(i,e,t)=>{let n=null,r=i.doc,s=i.scope;oe(r,l=>{for(;e.length>0&&i.currStackItem===null;){let a=r.store,c=e.pop(),h=new Set,f=[],u=!1;xn(l,c.insertions,d=>{if(d instanceof le){if(d.redone!==null){let{item:p,diff:O}=tf(a,d.id);O>0&&(p=Ue(l,W(p.id.client,p.id.clock+O))),d=p}!d.deleted&&s.some(p=>p===l.doc||Is(p,d))&&f.push(d)}}),xn(l,c.deletions,d=>{d instanceof le&&s.some(p=>p===l.doc||Is(p,d))&&!Mn(c.insertions,d.id)&&h.add(d)}),h.forEach(d=>{u=bm(l,d,h,c.insertions,i.ignoreRemoteMapChanges,i)!==null||u});for(let d=f.length-1;d>=0;d--){let p=f[d];i.deleteFilter(p)&&(p.delete(l),u=!0)}i.currStackItem=u?c:null}l.changed.forEach((a,c)=>{a.has(null)&&c._searchMarker&&(c._searchMarker.length=0)}),n=l},i);let o=i.currStackItem;if(o!=null){let l=n.changedParentTypes;i.emit("stack-item-popped",[{stackItem:o,type:t,changedParentTypes:l,origin:i},i]),i.currStackItem=null}return o},Bs=class extends sr{constructor(e,{captureTimeout:t=500,captureTransaction:n=a=>!0,deleteFilter:r=()=>!0,trackedOrigins:s=new Set([null]),ignoreRemoteMapChanges:o=!1,doc:l=ui(e)?e[0].doc:e instanceof _i?e:e.doc}={}){super(),this.scope=[],this.doc=l,this.addToScope(e),this.deleteFilter=r,s.add(this),this.trackedOrigins=s,this.captureTransaction=n,this.undoStack=[],this.redoStack=[],this.undoing=!1,this.redoing=!1,this.currStackItem=null,this.lastChange=0,this.ignoreRemoteMapChanges=o,this.captureTimeout=t,this.afterTransactionHandler=a=>{if(!this.captureTransaction(a)||!this.scope.some(g=>a.changedParentTypes.has(g)||g===this.doc)||!this.trackedOrigins.has(a.origin)&&(!a.origin||!this.trackedOrigins.has(a.origin.constructor)))return;let c=this.undoing,h=this.redoing,f=c?this.redoStack:this.undoStack;c?this.stopCapturing():h||this.clear(!1,!0);let u=new Zi;a.afterState.forEach((g,b)=>{let w=a.beforeState.get(b)||0,x=g-w;x>0&&js(u,b,w,x)});let d=pi(),p=!1;if(this.lastChange>0&&d-this.lastChange<this.captureTimeout&&f.length>0&&!c&&!h){let g=f[f.length-1];g.deletions=wn([g.deletions,a.deleteSet]),g.insertions=wn([g.insertions,u])}else f.push(new Jh(a.deleteSet,u)),p=!0;!c&&!h&&(this.lastChange=d),xn(a,a.deleteSet,g=>{g instanceof le&&this.scope.some(b=>b===a.doc||Is(b,g))&&mf(g,!0)});let O=[{stackItem:f[f.length-1],origin:a.origin,type:c?"redo":"undo",changedParentTypes:a.changedParentTypes},this];p?this.emit("stack-item-added",O):this.emit("stack-item-updated",O)},this.destroy=this.destroy.bind(this),this.doc.on("afterTransaction",this.afterTransactionHandler),this.doc.on("destroy",this.destroy)}addToScope(e){let t=new Set(this.scope);e=ui(e)?e:[e],e.forEach(n=>{t.has(n)||(t.add(n),(n instanceof ge?n.doc!==this.doc:n!==this.doc)&&zh("[yjs#509] Not same Y.Doc"),this.scope.push(n))})}addTrackedOrigin(e){this.trackedOrigins.add(e)}removeTrackedOrigin(e){this.trackedOrigins.delete(e)}clear(e=!0,t=!0){(e&&this.canUndo()||t&&this.canRedo())&&this.doc.transact(n=>{e&&(this.undoStack.forEach(r=>Pg(n,this,r)),this.undoStack=[]),t&&(this.redoStack.forEach(r=>Pg(n,this,r)),this.redoStack=[]),this.emit("stack-cleared",[{undoStackCleared:e,redoStackCleared:t}])})}stopCapturing(){this.lastChange=0}undo(){this.undoing=!0;let e;try{e=Cg(this,this.undoStack,"undo")}finally{this.undoing=!1}return e}redo(){this.redoing=!0;let e;try{e=Cg(this,this.redoStack,"redo")}finally{this.redoing=!1}return e}canUndo(){return this.undoStack.length>0}canRedo(){return this.redoStack.length>0}destroy(){this.trackedOrigins.delete(this),this.doc.off("afterTransaction",this.afterTransactionHandler),this.doc.off("destroy",this.destroy),super.destroy()}};function*QQ(i){let e=z(i.restDecoder);for(let t=0;t<e;t++){let n=z(i.restDecoder),r=i.readClient(),s=z(i.restDecoder);for(let o=0;o<n;o++){let l=i.readInfo();if(l===10){let a=z(i.restDecoder);yield new Xe(W(r,s),a),s+=a}else if((31&l)!==0){let a=(l&192)===0,c=new le(W(r,s),null,(l&128)===128?i.readLeftID():null,null,(l&64)===64?i.readRightID():null,a?i.readParentInfo()?i.readString():i.readLeftID():null,a&&(l&32)===32?i.readString():null,ym(i,l));yield c,s+=c.length}else{let a=i.readLen();yield new Be(W(r,s),a),s+=a}}}}var si=class{constructor(e,t){this.gen=QQ(e),this.curr=null,this.done=!1,this.filterSkips=t,this.next()}next(){do this.curr=this.gen.next().value||null;while(this.filterSkips&&this.curr!==null&&this.curr.constructor===Xe);return this.curr}},vQ=i=>Wg(i,ot),Wg=(i,e=Fe)=>{let t=[],n=new e(Qe(i)),r=new si(n,!1);for(let o=r.curr;o!==null;o=r.next())t.push(o);Xl("Structs: ",t);let s=gi(n);Xl("DeleteSet: ",s)},$Q=i=>Ng(i,ot),Ng=(i,e=Fe)=>{let t=[],n=new e(Qe(i)),r=new si(n,!1);for(let s=r.curr;s!==null;s=r.next())t.push(s);return{structs:t,ds:gi(n)}},Vs=class{constructor(e){this.currClient=0,this.startClock=0,this.written=0,this.encoder=e,this.clientStructs=[]}},qg=i=>Ys(i,ot,ri),Gg=(i,e=Cr,t=Fe)=>{let n=new e,r=new si(new t(Qe(i)),!1),s=r.curr;if(s!==null){let o=0,l=s.id.client,a=s.id.clock!==0,c=a?0:s.id.clock+s.length;for(;s!==null;s=r.next())l!==s.id.client&&(c!==0&&(o++,B(n.restEncoder,l),B(n.restEncoder,c)),l=s.id.client,c=0,a=s.id.clock!==0),s.constructor===Xe&&(a=!0),a||(c=s.id.clock+s.length);c!==0&&(o++,B(n.restEncoder,l),B(n.restEncoder,c));let h=di();return B(h,o),RO(h,n.restEncoder),n.restEncoder=h,n.toUint8Array()}else return B(n.restEncoder,0),n.toUint8Array()},PQ=i=>Gg(i,Li,ot),Fg=(i,e=Fe)=>{let t=new Map,n=new Map,r=new si(new e(Qe(i)),!1),s=r.curr;if(s!==null){let o=s.id.client,l=s.id.clock;for(t.set(o,l);s!==null;s=r.next())o!==s.id.client&&(n.set(o,l),t.set(s.id.client,s.id.clock),o=s.id.client),l=s.id.clock+s.length;n.set(o,l)}return{from:t,to:n}},CQ=i=>Fg(i,ot),TQ=(i,e)=>{if(i.constructor===Be){let{client:t,clock:n}=i.id;return new Be(W(t,n+e),i.length-e)}else if(i.constructor===Xe){let{client:t,clock:n}=i.id;return new Xe(W(t,n+e),i.length-e)}else{let t=i,{client:n,clock:r}=t.id;return new le(W(n,r+e),null,W(n,r+e-1),null,t.rightOrigin,t.parent,t.parentSub,t.content.splice(e))}},Ys=(i,e=Fe,t=Ot)=>{if(i.length===1)return i[0];let n=i.map(h=>new e(Qe(h))),r=n.map(h=>new si(h,!0)),s=null,o=new t,l=new Vs(o);for(;r=r.filter(u=>u.curr!==null),r.sort((u,d)=>{if(u.curr.id.client===d.curr.id.client){let p=u.curr.id.clock-d.curr.id.clock;return p===0?u.curr.constructor===d.curr.constructor?0:u.curr.constructor===Xe?1:-1:p}else return d.curr.id.client-u.curr.id.client}),r.length!==0;){let h=r[0],f=h.curr.id.client;if(s!==null){let u=h.curr,d=!1;for(;u!==null&&u.id.clock+u.length<=s.struct.id.clock+s.struct.length&&u.id.client>=s.struct.id.client;)u=h.next(),d=!0;if(u===null||u.id.client!==f||d&&u.id.clock>s.struct.id.clock+s.struct.length)continue;if(f!==s.struct.id.client)Xi(l,s.struct,s.offset),s={struct:u,offset:0},h.next();else if(s.struct.id.clock+s.struct.length<u.id.clock)if(s.struct.constructor===Xe)s.struct.length=u.id.clock+u.length-s.struct.id.clock;else{Xi(l,s.struct,s.offset);let p=u.id.clock-s.struct.id.clock-s.struct.length;s={struct:new Xe(W(f,s.struct.id.clock+s.struct.length),p),offset:0}}else{let p=s.struct.id.clock+s.struct.length-u.id.clock;p>0&&(s.struct.constructor===Xe?s.struct.length-=p:u=TQ(u,p)),s.struct.mergeWith(u)||(Xi(l,s.struct,s.offset),s={struct:u,offset:0},h.next())}}else s={struct:h.curr,offset:0},h.next();for(let u=h.curr;u!==null&&u.id.client===f&&u.id.clock===s.struct.id.clock+s.struct.length&&u.constructor!==Xe;u=h.next())Xi(l,s.struct,s.offset),s={struct:u,offset:0}}s!==null&&(Xi(l,s.struct,s.offset),s=null),df(l);let a=n.map(h=>gi(h)),c=wn(a);return ni(o,c),o.toUint8Array()},uf=(i,e,t=Fe,n=Ot)=>{let r=af(e),s=new n,o=new Vs(s),l=new t(Qe(i)),a=new si(l,!1);for(;a.curr;){let h=a.curr,f=h.id.client,u=r.get(f)||0;if(a.curr.constructor===Xe){a.next();continue}if(h.id.clock+h.length>u)for(Xi(o,h,Lt(u-h.id.clock,0)),a.next();a.curr&&a.curr.id.client===f;)Xi(o,a.curr,0),a.next();else for(;a.curr&&a.curr.id.client===f&&a.curr.id.clock+a.curr.length<=u;)a.next()}df(o);let c=gi(l);return ni(s,c),s.toUint8Array()},AQ=(i,e)=>uf(i,e,ot,ri),Hg=i=>{i.written>0&&(i.clientStructs.push({written:i.written,restEncoder:qe(i.encoder.restEncoder)}),i.encoder.restEncoder=di(),i.written=0)},Xi=(i,e,t)=>{i.written>0&&i.currClient!==e.id.client&&Hg(i),i.written===0&&(i.currClient=e.id.client,i.encoder.writeClient(e.id.client),B(i.encoder.restEncoder,e.id.clock+t)),e.write(i.encoder,t),i.written++},df=i=>{Hg(i);let e=i.encoder.restEncoder;B(e,i.clientStructs.length);for(let t=0;t<i.clientStructs.length;t++){let n=i.clientStructs[t];B(e,n.written),pr(e,n.restEncoder)}},ia=(i,e,t,n)=>{let r=new t(Qe(i)),s=new si(r,!1),o=new n,l=new Vs(o);for(let c=s.curr;c!==null;c=s.next())Xi(l,e(c),0);df(l);let a=gi(r);return ni(o,a),o.toUint8Array()},Kg=({formatting:i=!0,subdocs:e=!0,yxml:t=!0}={})=>{let n=0,r=pe(),s=pe(),o=pe(),l=pe();return l.set(null,null),a=>{switch(a.constructor){case Be:case Xe:return a;case le:{let c=a,h=c.content;switch(h.constructor){case Rr:break;case Pt:{if(t){let f=h.type;f instanceof Cn&&(f.nodeName=Ie(s,f.nodeName,()=>"node-"+n)),f instanceof qs&&(f.hookName=Ie(s,f.hookName,()=>"hook-"+n))}break}case Di:{let f=h;f.arr=f.arr.map(()=>n);break}case Tn:{let f=h;f.content=new Uint8Array([n]);break}case An:{let f=h;e&&(f.opts={},f.doc.guid=n+"");break}case mi:{let f=h;f.embed={};break}case me:{let f=h;i&&(f.key=Ie(o,f.key,()=>n+""),f.value=Ie(l,f.value,()=>({i:n})));break}case Gs:{let f=h;f.arr=f.arr.map(()=>n);break}case Dt:{let f=h;f.str=ul(n%10+"",f.str.length);break}default:Ce()}return c.parentSub&&(c.parentSub=Ie(r,c.parentSub,()=>n+"")),n++,a}default:Ce()}}},MQ=(i,e)=>ia(i,Kg(e),ot,ri),RQ=(i,e)=>ia(i,Kg(e),Fe,Ot),XQ=i=>ia(i,vh,ot,Ot),Jg=i=>ia(i,vh,Fe,ri),Tg="You must not compute changes after the event-handler fired.",Qn=class{constructor(e,t){this.target=e,this.currentTarget=e,this.transaction=t,this._changes=null,this._keys=null,this._delta=null,this._path=null}get path(){return this._path||(this._path=EQ(this.currentTarget,this.target))}deletes(e){return Mn(this.transaction.deleteSet,e.id)}get keys(){if(this._keys===null){if(this.transaction.doc._transactionCleanups.length===0)throw dt(Tg);let e=new Map,t=this.target;this.transaction.changed.get(t).forEach(r=>{if(r!==null){let s=t._map.get(r),o,l;if(this.adds(s)){let a=s.left;for(;a!==null&&this.adds(a);)a=a.left;if(this.deletes(s))if(a!==null&&this.deletes(a))o="delete",l=al(a.content.getContent());else return;else a!==null&&this.deletes(a)?(o="update",l=al(a.content.getContent())):(o="add",l=void 0)}else if(this.deletes(s))o="delete",l=al(s.content.getContent());else return;e.set(r,{action:o,oldValue:l})}}),this._keys=e}return this._keys}get delta(){return this.changes.delta}adds(e){return e.id.clock>=(this.transaction.beforeState.get(e.id.client)||0)}get changes(){let e=this._changes;if(e===null){if(this.transaction.doc._transactionCleanups.length===0)throw dt(Tg);let t=this.target,n=Ht(),r=Ht(),s=[];if(e={added:n,deleted:r,delta:s,keys:this.keys},this.transaction.changed.get(t).has(null)){let l=null,a=()=>{l&&s.push(l)};for(let c=t._start;c!==null;c=c.right)c.deleted?this.deletes(c)&&!this.adds(c)&&((l===null||l.delete===void 0)&&(a(),l={delete:0}),l.delete+=c.length,r.add(c)):this.adds(c)?((l===null||l.insert===void 0)&&(a(),l={insert:[]}),l.insert=l.insert.concat(c.content.getContent()),n.add(c)):((l===null||l.retain===void 0)&&(a(),l={retain:0}),l.retain+=c.length);l!==null&&l.retain===void 0&&a()}this._changes=e}return e}},EQ=(i,e)=>{let t=[];for(;e._item!==null&&e!==i;){if(e._item.parentSub!==null)t.unshift(e._item.parentSub);else{let n=0,r=e._item.parent._start;for(;r!==e._item&&r!==null;)!r.deleted&&r.countable&&(n+=r.length),r=r.right;t.unshift(n)}e=e._item.parent}return t},Ee=()=>{zh("Invalid access: Add Yjs type to a document before reading data.")},em=80,pf=0,ef=class{constructor(e,t){e.marker=!0,this.p=e,this.index=t,this.timestamp=pf++}},ZQ=i=>{i.timestamp=pf++},tm=(i,e,t)=>{i.p.marker=!1,i.p=e,e.marker=!0,i.index=t,i.timestamp=pf++},_Q=(i,e,t)=>{if(i.length>=em){let n=i.reduce((r,s)=>r.timestamp<s.timestamp?r:s);return tm(n,e,t),n}else{let n=new ef(e,t);return i.push(n),n}},na=(i,e)=>{if(i._start===null||e===0||i._searchMarker===null)return null;let t=i._searchMarker.length===0?null:i._searchMarker.reduce((s,o)=>un(e-s.index)<un(e-o.index)?s:o),n=i._start,r=0;for(t!==null&&(n=t.p,r=t.index,ZQ(t));n.right!==null&&r<e;){if(!n.deleted&&n.countable){if(e<r+n.length)break;r+=n.length}n=n.right}for(;n.left!==null&&r>e;)n=n.left,!n.deleted&&n.countable&&(r-=n.length);for(;n.left!==null&&n.left.id.client===n.id.client&&n.left.id.clock+n.left.length===n.id.clock;)n=n.left,!n.deleted&&n.countable&&(r-=n.length);return t!==null&&un(t.index-r)<n.parent.length/em?(tm(t,n,r),t):_Q(i._searchMarker,n,r)},zs=(i,e,t)=>{for(let n=i.length-1;n>=0;n--){let r=i[n];if(t>0){let s=r.p;for(s.marker=!1;s&&(s.deleted||!s.countable);)s=s.left,s&&!s.deleted&&s.countable&&(r.index-=s.length);if(s===null||s.marker===!0){i.splice(n,1);continue}r.p=s,s.marker=!0}(e<r.index||t>0&&e===r.index)&&(r.index=Lt(e,r.index+t))}},LQ=i=>{i.doc??Ee();let e=i._start,t=[];for(;e;)t.push(e),e=e.right;return t},ra=(i,e,t)=>{let n=i,r=e.changedParentTypes;for(;Ie(r,i,()=>[]).push(t),i._item!==null;)i=i._item.parent;Dg(n._eH,t,e)},ge=class{constructor(){this._item=null,this._map=new Map,this._start=null,this.doc=null,this._length=0,this._eH=Sg(),this._dEH=Sg(),this._searchMarker=null}get parent(){return this._item?this._item.parent:null}_integrate(e,t){this.doc=e,this._item=t}_copy(){throw pt()}clone(){throw pt()}_write(e){}get _first(){let e=this._start;for(;e!==null&&e.deleted;)e=e.right;return e}_callObserver(e,t){!e.local&&this._searchMarker&&(this._searchMarker.length=0)}observe(e){xg(this._eH,e)}observeDeep(e){xg(this._dEH,e)}unobserve(e){wg(this._eH,e)}unobserveDeep(e){wg(this._dEH,e)}toJSON(){}},im=(i,e,t)=>{i.doc??Ee(),e<0&&(e=i._length+e),t<0&&(t=i._length+t);let n=t-e,r=[],s=i._start;for(;s!==null&&n>0;){if(s.countable&&!s.deleted){let o=s.content.getContent();if(o.length<=e)e-=o.length;else{for(let l=e;l<o.length&&n>0;l++)r.push(o[l]),n--;e=0}}s=s.right}return r},nm=i=>{i.doc??Ee();let e=[],t=i._start;for(;t!==null;){if(t.countable&&!t.deleted){let n=t.content.getContent();for(let r=0;r<n.length;r++)e.push(n[r])}t=t.right}return e},DQ=(i,e)=>{let t=[],n=i._start;for(;n!==null;){if(n.countable&&Ri(n,e)){let r=n.content.getContent();for(let s=0;s<r.length;s++)t.push(r[s])}n=n.right}return t},Ws=(i,e)=>{let t=0,n=i._start;for(i.doc??Ee();n!==null;){if(n.countable&&!n.deleted){let r=n.content.getContent();for(let s=0;s<r.length;s++)e(r[s],t++,i)}n=n.right}},rm=(i,e)=>{let t=[];return Ws(i,(n,r)=>{t.push(e(n,r,i))}),t},jQ=i=>{let e=i._start,t=null,n=0;return{[Symbol.iterator](){return this},next:()=>{if(t===null){for(;e!==null&&e.deleted;)e=e.right;if(e===null)return{done:!0,value:void 0};t=e.content.getContent(),n=0,e=e.right}let r=t[n++];return t.length<=n&&(t=null),{done:!1,value:r}}}},sm=(i,e)=>{i.doc??Ee();let t=na(i,e),n=i._start;for(t!==null&&(n=t.p,e-=t.index);n!==null;n=n.right)if(!n.deleted&&n.countable){if(e<n.length)return n.content.getContent()[e];e-=n.length}},Yl=(i,e,t,n)=>{let r=t,s=i.doc,o=s.clientID,l=s.store,a=t===null?e._start:t.right,c=[],h=()=>{c.length>0&&(r=new le(W(o,ue(l,o)),r,r&&r.lastId,a,a&&a.id,e,null,new Di(c)),r.integrate(i,0),c=[])};n.forEach(f=>{if(f===null)c.push(f);else switch(f.constructor){case Number:case Object:case Boolean:case Array:case String:c.push(f);break;default:switch(h(),f.constructor){case Uint8Array:case ArrayBuffer:r=new le(W(o,ue(l,o)),r,r&&r.lastId,a,a&&a.id,e,null,new Tn(new Uint8Array(f))),r.integrate(i,0);break;case _i:r=new le(W(o,ue(l,o)),r,r&&r.lastId,a,a&&a.id,e,null,new An(f)),r.integrate(i,0);break;default:if(f instanceof ge)r=new le(W(o,ue(l,o)),r,r&&r.lastId,a,a&&a.id,e,null,new Pt(f)),r.integrate(i,0);else throw new Error("Unexpected content type in insert operation")}}}),h()},om=()=>dt("Length exceeded!"),lm=(i,e,t,n)=>{if(t>e._length)throw om();if(t===0)return e._searchMarker&&zs(e._searchMarker,t,n.length),Yl(i,e,null,n);let r=t,s=na(e,t),o=e._start;for(s!==null&&(o=s.p,t-=s.index,t===0&&(o=o.prev,t+=o&&o.countable&&!o.deleted?o.length:0));o!==null;o=o.right)if(!o.deleted&&o.countable){if(t<=o.length){t<o.length&&Ue(i,W(o.id.client,o.id.clock+t));break}t-=o.length}return e._searchMarker&&zs(e._searchMarker,r,n.length),Yl(i,e,o,n)},IQ=(i,e,t)=>{let r=(e._searchMarker||[]).reduce((s,o)=>o.index>s.index?o:s,{index:0,p:e._start}).p;if(r)for(;r.right;)r=r.right;return Yl(i,e,r,t)},am=(i,e,t,n)=>{if(n===0)return;let r=t,s=n,o=na(e,t),l=e._start;for(o!==null&&(l=o.p,t-=o.index);l!==null&&t>0;l=l.right)!l.deleted&&l.countable&&(t<l.length&&Ue(i,W(l.id.client,l.id.clock+t)),t-=l.length);for(;n>0&&l!==null;)l.deleted||(n<l.length&&Ue(i,W(l.id.client,l.id.clock+n)),l.delete(i),n-=l.length),l=l.right;if(n>0)throw om();e._searchMarker&&zs(e._searchMarker,r,-s+n)},zl=(i,e,t)=>{let n=e._map.get(t);n!==void 0&&n.delete(i)},Of=(i,e,t,n)=>{let r=e._map.get(t)||null,s=i.doc,o=s.clientID,l;if(n==null)l=new Di([n]);else switch(n.constructor){case Number:case Object:case Boolean:case Array:case String:case Date:case BigInt:l=new Di([n]);break;case Uint8Array:l=new Tn(n);break;case _i:l=new An(n);break;default:if(n instanceof ge)l=new Pt(n);else throw new Error("Unexpected content type")}new le(W(o,ue(s.store,o)),r,r&&r.lastId,null,null,e,t,l).integrate(i,0)},gf=(i,e)=>{i.doc??Ee();let t=i._map.get(e);return t!==void 0&&!t.deleted?t.content.getContent()[t.length-1]:void 0},cm=i=>{let e={};return i.doc??Ee(),i._map.forEach((t,n)=>{t.deleted||(e[n]=t.content.getContent()[t.length-1])}),e},hm=(i,e)=>{i.doc??Ee();let t=i._map.get(e);return t!==void 0&&!t.deleted},UQ=(i,e,t)=>{let n=i._map.get(e)||null;for(;n!==null&&(!t.sv.has(n.id.client)||n.id.clock>=(t.sv.get(n.id.client)||0));)n=n.left;return n!==null&&Ri(n,t)?n.content.getContent()[n.length-1]:void 0},fm=(i,e)=>{let t={};return i._map.forEach((n,r)=>{let s=n;for(;s!==null&&(!e.sv.has(s.id.client)||s.id.clock>=(e.sv.get(s.id.client)||0));)s=s.left;s!==null&&Ri(s,e)&&(t[r]=s.content.getContent()[s.length-1])}),t},_l=i=>(i.doc??Ee(),gg(i._map.entries(),e=>!e[1].deleted)),Wl=class extends Qn{},vn=class i extends ge{constructor(){super(),this._prelimContent=[],this._searchMarker=[]}static from(e){let t=new i;return t.push(e),t}_integrate(e,t){super._integrate(e,t),this.insert(0,this._prelimContent),this._prelimContent=null}_copy(){return new i}clone(){let e=new i;return e.insert(0,this.toArray().map(t=>t instanceof ge?t.clone():t)),e}get length(){return this.doc??Ee(),this._length}_callObserver(e,t){super._callObserver(e,t),ra(this,e,new Wl(this,e))}insert(e,t){this.doc!==null?oe(this.doc,n=>{lm(n,this,e,t)}):this._prelimContent.splice(e,0,...t)}push(e){this.doc!==null?oe(this.doc,t=>{IQ(t,this,e)}):this._prelimContent.push(...e)}unshift(e){this.insert(0,e)}delete(e,t=1){this.doc!==null?oe(this.doc,n=>{am(n,this,e,t)}):this._prelimContent.splice(e,t)}get(e){return sm(this,e)}toArray(){return nm(this)}slice(e=0,t=this.length){return im(this,e,t)}toJSON(){return this.map(e=>e instanceof ge?e.toJSON():e)}map(e){return rm(this,e)}forEach(e){Ws(this,e)}[Symbol.iterator](){return jQ(this)}_write(e){e.writeTypeRef(av)}},BQ=i=>new vn,Nl=class extends Qn{constructor(e,t,n){super(e,t),this.keysChanged=n}},$n=class i extends ge{constructor(e){super(),this._prelimContent=null,e===void 0?this._prelimContent=new Map:this._prelimContent=new Map(e)}_integrate(e,t){super._integrate(e,t),this._prelimContent.forEach((n,r)=>{this.set(r,n)}),this._prelimContent=null}_copy(){return new i}clone(){let e=new i;return this.forEach((t,n)=>{e.set(n,t instanceof ge?t.clone():t)}),e}_callObserver(e,t){ra(this,e,new Nl(this,e,t))}toJSON(){this.doc??Ee();let e={};return this._map.forEach((t,n)=>{if(!t.deleted){let r=t.content.getContent()[t.length-1];e[n]=r instanceof ge?r.toJSON():r}}),e}get size(){return[..._l(this)].length}keys(){return El(_l(this),e=>e[0])}values(){return El(_l(this),e=>e[1].content.getContent()[e[1].length-1])}entries(){return El(_l(this),e=>[e[0],e[1].content.getContent()[e[1].length-1]])}forEach(e){this.doc??Ee(),this._map.forEach((t,n)=>{t.deleted||e(t.content.getContent()[t.length-1],n,this)})}[Symbol.iterator](){return this.entries()}delete(e){this.doc!==null?oe(this.doc,t=>{zl(t,this,e)}):this._prelimContent.delete(e)}set(e,t){return this.doc!==null?oe(this.doc,n=>{Of(n,this,e,t)}):this._prelimContent.set(e,t),t}get(e){return gf(this,e)}has(e){return hm(this,e)}clear(){this.doc!==null?oe(this.doc,e=>{this.forEach(function(t,n,r){zl(e,r,n)})}):this._prelimContent.clear()}_write(e){e.writeTypeRef(cv)}},VQ=i=>new $n,Ei=(i,e)=>i===e||typeof i=="object"&&typeof e=="object"&&i&&e&&wh(i,e),Ns=class{constructor(e,t,n,r){this.left=e,this.right=t,this.index=n,this.currentAttributes=r}forward(){this.right===null&&Ce(),this.right.content.constructor===me?this.right.deleted||Zr(this.currentAttributes,this.right.content):this.right.deleted||(this.index+=this.right.length),this.left=this.right,this.right=this.right.right}},Ag=(i,e,t)=>{for(;e.right!==null&&t>0;)e.right.content.constructor===me?e.right.deleted||Zr(e.currentAttributes,e.right.content):e.right.deleted||(t<e.right.length&&Ue(i,W(e.right.id.client,e.right.id.clock+t)),e.index+=e.right.length,t-=e.right.length),e.left=e.right,e.right=e.right.right;return e},Ll=(i,e,t,n)=>{let r=new Map,s=n?na(e,t):null;if(s){let o=new Ns(s.p.left,s.p,s.index,r);return Ag(i,o,t-s.index)}else{let o=new Ns(null,e._start,0,r);return Ag(i,o,t)}},um=(i,e,t,n)=>{for(;t.right!==null&&(t.right.deleted===!0||t.right.content.constructor===me&&Ei(n.get(t.right.content.key),t.right.content.value));)t.right.deleted||n.delete(t.right.content.key),t.forward();let r=i.doc,s=r.clientID;n.forEach((o,l)=>{let a=t.left,c=t.right,h=new le(W(s,ue(r.store,s)),a,a&&a.lastId,c,c&&c.id,e,null,new me(l,o));h.integrate(i,0),t.right=h,t.forward()})},Zr=(i,e)=>{let{key:t,value:n}=e;n===null?i.delete(t):i.set(t,n)},dm=(i,e)=>{for(;i.right!==null;){if(!(i.right.deleted||i.right.content.constructor===me&&Ei(e[i.right.content.key]??null,i.right.content.value)))break;i.forward()}},pm=(i,e,t,n)=>{let r=i.doc,s=r.clientID,o=new Map;for(let l in n){let a=n[l],c=t.currentAttributes.get(l)??null;if(!Ei(c,a)){o.set(l,c);let{left:h,right:f}=t;t.right=new le(W(s,ue(r.store,s)),h,h&&h.lastId,f,f&&f.id,e,null,new me(l,a)),t.right.integrate(i,0),t.forward()}}return o},Wh=(i,e,t,n,r)=>{t.currentAttributes.forEach((u,d)=>{r[d]===void 0&&(r[d]=null)});let s=i.doc,o=s.clientID;dm(t,r);let l=pm(i,e,t,r),a=n.constructor===String?new Dt(n):n instanceof ge?new Pt(n):new mi(n),{left:c,right:h,index:f}=t;e._searchMarker&&zs(e._searchMarker,t.index,a.getLength()),h=new le(W(o,ue(s.store,o)),c,c&&c.lastId,h,h&&h.id,e,null,a),h.integrate(i,0),t.right=h,t.index=f,t.forward(),um(i,e,t,l)},Mg=(i,e,t,n,r)=>{let s=i.doc,o=s.clientID;dm(t,r);let l=pm(i,e,t,r);e:for(;t.right!==null&&(n>0||l.size>0&&(t.right.deleted||t.right.content.constructor===me));){if(!t.right.deleted)switch(t.right.content.constructor){case me:{let{key:a,value:c}=t.right.content,h=r[a];if(h!==void 0){if(Ei(h,c))l.delete(a);else{if(n===0)break e;l.set(a,c)}t.right.delete(i)}else t.currentAttributes.set(a,c);break}default:n<t.right.length&&Ue(i,W(t.right.id.client,t.right.id.clock+n)),n-=t.right.length;break}t.forward()}if(n>0){let a="";for(;n>0;n--)a+=` +`;t.right=new le(W(o,ue(s.store,o)),t.left,t.left&&t.left.lastId,t.right,t.right&&t.right.id,e,null,new Dt(a)),t.right.integrate(i,0),t.forward()}um(i,e,t,l)},Om=(i,e,t,n,r)=>{let s=e,o=pe();for(;s&&(!s.countable||s.deleted);){if(!s.deleted&&s.content.constructor===me){let c=s.content;o.set(c.key,c)}s=s.right}let l=0,a=!1;for(;e!==s;){if(t===e&&(a=!0),!e.deleted){let c=e.content;if(c.constructor===me){let{key:h,value:f}=c,u=n.get(h)??null;(o.get(h)!==c||u===f)&&(e.delete(i),l++,!a&&(r.get(h)??null)===f&&u!==f&&(u===null?r.delete(h):r.set(h,u))),!a&&!e.deleted&&Zr(r,c)}}e=e.right}return l},YQ=(i,e)=>{for(;e&&e.right&&(e.right.deleted||!e.right.countable);)e=e.right;let t=new Set;for(;e&&(e.deleted||!e.countable);){if(!e.deleted&&e.content.constructor===me){let n=e.content.key;t.has(n)?e.delete(i):t.add(n)}e=e.left}},gm=i=>{let e=0;return oe(i.doc,t=>{let n=i._start,r=i._start,s=pe(),o=ll(s);for(;r;)r.deleted===!1&&(r.content.constructor===me?Zr(o,r.content):(e+=Om(t,n,r,s,o),s=ll(o),n=r)),r=r.right}),e},zQ=i=>{let e=new Set,t=i.doc;for(let[n,r]of i.afterState.entries()){let s=i.beforeState.get(n)||0;r!==s&&Bg(i,t.store.clients.get(n),s,r,o=>{!o.deleted&&o.content.constructor===me&&o.constructor!==Be&&e.add(o.parent)})}oe(t,n=>{xn(i,i.deleteSet,r=>{if(r instanceof Be||!r.parent._hasFormatting||e.has(r.parent))return;let s=r.parent;r.content.constructor===me?e.add(s):YQ(n,r)});for(let r of e)gm(r)})},Rg=(i,e,t)=>{let n=t,r=ll(e.currentAttributes),s=e.right;for(;t>0&&e.right!==null;){if(e.right.deleted===!1)switch(e.right.content.constructor){case Pt:case mi:case Dt:t<e.right.length&&Ue(i,W(e.right.id.client,e.right.id.clock+t)),t-=e.right.length,e.right.delete(i);break}e.forward()}s&&Om(i,s,e.right,r,e.currentAttributes);let o=(e.left||e.right).parent;return o._searchMarker&&zs(o._searchMarker,e.index,-n+t),e},ql=class extends Qn{constructor(e,t,n){super(e,t),this.childListChanged=!1,this.keysChanged=new Set,n.forEach(r=>{r===null?this.childListChanged=!0:this.keysChanged.add(r)})}get changes(){if(this._changes===null){let e={keys:this.keys,delta:this.delta,added:new Set,deleted:new Set};this._changes=e}return this._changes}get delta(){if(this._delta===null){let e=this.target.doc,t=[];oe(e,n=>{let r=new Map,s=new Map,o=this.target._start,l=null,a={},c="",h=0,f=0,u=()=>{if(l!==null){let d=null;switch(l){case"delete":f>0&&(d={delete:f}),f=0;break;case"insert":(typeof c=="object"||c.length>0)&&(d={insert:c},r.size>0&&(d.attributes={},r.forEach((p,O)=>{p!==null&&(d.attributes[O]=p)}))),c="";break;case"retain":h>0&&(d={retain:h},VO(a)||(d.attributes=UO({},a))),h=0;break}d&&t.push(d),l=null}};for(;o!==null;){switch(o.content.constructor){case Pt:case mi:this.adds(o)?this.deletes(o)||(u(),l="insert",c=o.content.getContent()[0],u()):this.deletes(o)?(l!=="delete"&&(u(),l="delete"),f+=1):o.deleted||(l!=="retain"&&(u(),l="retain"),h+=1);break;case Dt:this.adds(o)?this.deletes(o)||(l!=="insert"&&(u(),l="insert"),c+=o.content.str):this.deletes(o)?(l!=="delete"&&(u(),l="delete"),f+=o.length):o.deleted||(l!=="retain"&&(u(),l="retain"),h+=o.length);break;case me:{let{key:d,value:p}=o.content;if(this.adds(o)){if(!this.deletes(o)){let O=r.get(d)??null;Ei(O,p)?p!==null&&o.delete(n):(l==="retain"&&u(),Ei(p,s.get(d)??null)?delete a[d]:a[d]=p)}}else if(this.deletes(o)){s.set(d,p);let O=r.get(d)??null;Ei(O,p)||(l==="retain"&&u(),a[d]=O)}else if(!o.deleted){s.set(d,p);let O=a[d];O!==void 0&&(Ei(O,p)?O!==null&&o.delete(n):(l==="retain"&&u(),p===null?delete a[d]:a[d]=p))}o.deleted||(l==="insert"&&u(),Zr(r,o.content));break}}o=o.right}for(u();t.length>0;){let d=t[t.length-1];if(d.retain!==void 0&&d.attributes===void 0)t.pop();else break}}),this._delta=t}return this._delta}},Ar=class i extends ge{constructor(e){super(),this._pending=e!==void 0?[()=>this.insert(0,e)]:[],this._searchMarker=[],this._hasFormatting=!1}get length(){return this.doc??Ee(),this._length}_integrate(e,t){super._integrate(e,t);try{this._pending.forEach(n=>n())}catch(n){console.error(n)}this._pending=null}_copy(){return new i}clone(){let e=new i;return e.applyDelta(this.toDelta()),e}_callObserver(e,t){super._callObserver(e,t);let n=new ql(this,e,t);ra(this,e,n),!e.local&&this._hasFormatting&&(e._needFormattingCleanup=!0)}toString(){this.doc??Ee();let e="",t=this._start;for(;t!==null;)!t.deleted&&t.countable&&t.content.constructor===Dt&&(e+=t.content.str),t=t.right;return e}toJSON(){return this.toString()}applyDelta(e,{sanitize:t=!0}={}){this.doc!==null?oe(this.doc,n=>{let r=new Ns(null,this._start,0,new Map);for(let s=0;s<e.length;s++){let o=e[s];if(o.insert!==void 0){let l=!t&&typeof o.insert=="string"&&s===e.length-1&&r.right===null&&o.insert.slice(-1)===` +`?o.insert.slice(0,-1):o.insert;(typeof l!="string"||l.length>0)&&Wh(n,this,r,l,o.attributes||{})}else o.retain!==void 0?Mg(n,this,r,o.retain,o.attributes||{}):o.delete!==void 0&&Rg(n,r,o.delete)}}):this._pending.push(()=>this.applyDelta(e))}toDelta(e,t,n){this.doc??Ee();let r=[],s=new Map,o=this.doc,l="",a=this._start;function c(){if(l.length>0){let f={},u=!1;s.forEach((p,O)=>{u=!0,f[O]=p});let d={insert:l};u&&(d.attributes=f),r.push(d),l=""}}let h=()=>{for(;a!==null;){if(Ri(a,e)||t!==void 0&&Ri(a,t))switch(a.content.constructor){case Dt:{let f=s.get("ychange");e!==void 0&&!Ri(a,e)?(f===void 0||f.user!==a.id.client||f.type!=="removed")&&(c(),s.set("ychange",n?n("removed",a.id):{type:"removed"})):t!==void 0&&!Ri(a,t)?(f===void 0||f.user!==a.id.client||f.type!=="added")&&(c(),s.set("ychange",n?n("added",a.id):{type:"added"})):f!==void 0&&(c(),s.delete("ychange")),l+=a.content.str;break}case Pt:case mi:{c();let f={insert:a.content.getContent()[0]};if(s.size>0){let u={};f.attributes=u,s.forEach((d,p)=>{u[p]=d})}r.push(f);break}case me:Ri(a,e)&&(c(),Zr(s,a.content));break}a=a.right}c()};return e||t?oe(o,f=>{e&&Fh(f,e),t&&Fh(f,t),h()},"cleanup"):h(),r}insert(e,t,n){if(t.length<=0)return;let r=this.doc;r!==null?oe(r,s=>{let o=Ll(s,this,e,!n);n||(n={},o.currentAttributes.forEach((l,a)=>{n[a]=l})),Wh(s,this,o,t,n)}):this._pending.push(()=>this.insert(e,t,n))}insertEmbed(e,t,n){let r=this.doc;r!==null?oe(r,s=>{let o=Ll(s,this,e,!n);Wh(s,this,o,t,n||{})}):this._pending.push(()=>this.insertEmbed(e,t,n||{}))}delete(e,t){if(t===0)return;let n=this.doc;n!==null?oe(n,r=>{Rg(r,Ll(r,this,e,!0),t)}):this._pending.push(()=>this.delete(e,t))}format(e,t,n){if(t===0)return;let r=this.doc;r!==null?oe(r,s=>{let o=Ll(s,this,e,!1);o.right!==null&&Mg(s,this,o,t,n)}):this._pending.push(()=>this.format(e,t,n))}removeAttribute(e){this.doc!==null?oe(this.doc,t=>{zl(t,this,e)}):this._pending.push(()=>this.removeAttribute(e))}setAttribute(e,t){this.doc!==null?oe(this.doc,n=>{Of(n,this,e,t)}):this._pending.push(()=>this.setAttribute(e,t))}getAttribute(e){return gf(this,e)}getAttributes(){return cm(this)}_write(e){e.writeTypeRef(hv)}},WQ=i=>new Ar,Ds=class{constructor(e,t=()=>!0){this._filter=t,this._root=e,this._currentNode=e._start,this._firstCall=!0,e.doc??Ee()}[Symbol.iterator](){return this}next(){let e=this._currentNode,t=e&&e.content&&e.content.type;if(e!==null&&(!this._firstCall||e.deleted||!this._filter(t)))do if(t=e.content.type,!e.deleted&&(t.constructor===Cn||t.constructor===Pn)&&t._start!==null)e=t._start;else for(;e!==null;){let n=e.next;if(n!==null){e=n;break}else e.parent===this._root?e=null:e=e.parent._item}while(e!==null&&(e.deleted||!this._filter(e.content.type)));return this._firstCall=!1,e===null?{value:void 0,done:!0}:(this._currentNode=e,{value:e.content.type,done:!1})}},Pn=class i extends ge{constructor(){super(),this._prelimContent=[]}get firstChild(){let e=this._first;return e?e.content.getContent()[0]:null}_integrate(e,t){super._integrate(e,t),this.insert(0,this._prelimContent),this._prelimContent=null}_copy(){return new i}clone(){let e=new i;return e.insert(0,this.toArray().map(t=>t instanceof ge?t.clone():t)),e}get length(){return this.doc??Ee(),this._prelimContent===null?this._length:this._prelimContent.length}createTreeWalker(e){return new Ds(this,e)}querySelector(e){e=e.toUpperCase();let n=new Ds(this,r=>r.nodeName&&r.nodeName.toUpperCase()===e).next();return n.done?null:n.value}querySelectorAll(e){return e=e.toUpperCase(),_t(new Ds(this,t=>t.nodeName&&t.nodeName.toUpperCase()===e))}_callObserver(e,t){ra(this,e,new Gl(this,t,e))}toString(){return rm(this,e=>e.toString()).join("")}toJSON(){return this.toString()}toDOM(e=document,t={},n){let r=e.createDocumentFragment();return n!==void 0&&n._createAssociation(r,this),Ws(this,s=>{r.insertBefore(s.toDOM(e,t,n),null)}),r}insert(e,t){this.doc!==null?oe(this.doc,n=>{lm(n,this,e,t)}):this._prelimContent.splice(e,0,...t)}insertAfter(e,t){if(this.doc!==null)oe(this.doc,n=>{let r=e&&e instanceof ge?e._item:e;Yl(n,this,r,t)});else{let n=this._prelimContent,r=e===null?0:n.findIndex(s=>s===e)+1;if(r===0&&e!==null)throw dt("Reference item not found");n.splice(r,0,...t)}}delete(e,t=1){this.doc!==null?oe(this.doc,n=>{am(n,this,e,t)}):this._prelimContent.splice(e,t)}toArray(){return nm(this)}push(e){this.insert(this.length,e)}unshift(e){this.insert(0,e)}get(e){return sm(this,e)}slice(e=0,t=this.length){return im(this,e,t)}forEach(e){Ws(this,e)}_write(e){e.writeTypeRef(uv)}},NQ=i=>new Pn,Cn=class i extends Pn{constructor(e="UNDEFINED"){super(),this.nodeName=e,this._prelimAttrs=new Map}get nextSibling(){let e=this._item?this._item.next:null;return e?e.content.type:null}get prevSibling(){let e=this._item?this._item.prev:null;return e?e.content.type:null}_integrate(e,t){super._integrate(e,t),this._prelimAttrs.forEach((n,r)=>{this.setAttribute(r,n)}),this._prelimAttrs=null}_copy(){return new i(this.nodeName)}clone(){let e=new i(this.nodeName),t=this.getAttributes();return BO(t,(n,r)=>{e.setAttribute(r,n)}),e.insert(0,this.toArray().map(n=>n instanceof ge?n.clone():n)),e}toString(){let e=this.getAttributes(),t=[],n=[];for(let l in e)n.push(l);n.sort();let r=n.length;for(let l=0;l<r;l++){let a=n[l];t.push(a+'="'+e[a]+'"')}let s=this.nodeName.toLocaleLowerCase(),o=t.length>0?" "+t.join(" "):"";return`<${s}${o}>${super.toString()}</${s}>`}removeAttribute(e){this.doc!==null?oe(this.doc,t=>{zl(t,this,e)}):this._prelimAttrs.delete(e)}setAttribute(e,t){this.doc!==null?oe(this.doc,n=>{Of(n,this,e,t)}):this._prelimAttrs.set(e,t)}getAttribute(e){return gf(this,e)}hasAttribute(e){return hm(this,e)}getAttributes(e){return e?fm(this,e):cm(this)}toDOM(e=document,t={},n){let r=e.createElement(this.nodeName),s=this.getAttributes();for(let o in s){let l=s[o];typeof l=="string"&&r.setAttribute(o,l)}return Ws(this,o=>{r.appendChild(o.toDOM(e,t,n))}),n!==void 0&&n._createAssociation(r,this),r}_write(e){e.writeTypeRef(fv),e.writeKey(this.nodeName)}},qQ=i=>new Cn(i.readKey()),Gl=class extends Qn{constructor(e,t,n){super(e,n),this.childListChanged=!1,this.attributesChanged=new Set,t.forEach(r=>{r===null?this.childListChanged=!0:this.attributesChanged.add(r)})}},qs=class i extends $n{constructor(e){super(),this.hookName=e}_copy(){return new i(this.hookName)}clone(){let e=new i(this.hookName);return this.forEach((t,n)=>{e.set(n,t)}),e}toDOM(e=document,t={},n){let r=t[this.hookName],s;return r!==void 0?s=r.createDom(this):s=document.createElement(this.hookName),s.setAttribute("data-yjs-hook",this.hookName),n!==void 0&&n._createAssociation(s,this),s}_write(e){e.writeTypeRef(dv),e.writeKey(this.hookName)}},GQ=i=>new qs(i.readKey()),Fl=class i extends Ar{get nextSibling(){let e=this._item?this._item.next:null;return e?e.content.type:null}get prevSibling(){let e=this._item?this._item.prev:null;return e?e.content.type:null}_copy(){return new i}clone(){let e=new i;return e.applyDelta(this.toDelta()),e}toDOM(e=document,t,n){let r=e.createTextNode(this.toString());return n!==void 0&&n._createAssociation(r,this),r}toString(){return this.toDelta().map(e=>{let t=[];for(let r in e.attributes){let s=[];for(let o in e.attributes[r])s.push({key:o,value:e.attributes[r][o]});s.sort((o,l)=>o.key<l.key?-1:1),t.push({nodeName:r,attrs:s})}t.sort((r,s)=>r.nodeName<s.nodeName?-1:1);let n="";for(let r=0;r<t.length;r++){let s=t[r];n+=`<${s.nodeName}`;for(let o=0;o<s.attrs.length;o++){let l=s.attrs[o];n+=` ${l.key}="${l.value}"`}n+=">"}n+=e.insert;for(let r=t.length-1;r>=0;r--)n+=`</${t[r].nodeName}>`;return n}).join("")}toJSON(){return this.toString()}_write(e){e.writeTypeRef(pv)}},FQ=i=>new Fl,Mr=class{constructor(e,t){this.id=e,this.length=t}get deleted(){throw pt()}mergeWith(e){return!1}write(e,t,n){throw pt()}integrate(e,t){throw pt()}},HQ=0,Be=class extends Mr{get deleted(){return!0}delete(){}mergeWith(e){return this.constructor!==e.constructor?!1:(this.length+=e.length,!0)}integrate(e,t){t>0&&(this.id.clock+=t,this.length-=t),Ug(e.doc.store,this)}write(e,t){e.writeInfo(HQ),e.writeLen(this.length-t)}getMissing(e,t){return null}},Tn=class i{constructor(e){this.content=e}getLength(){return 1}getContent(){return[this.content]}isCountable(){return!0}copy(){return new i(this.content)}splice(e){throw pt()}mergeWith(e){return!1}integrate(e,t){}delete(e){}gc(e){}write(e,t){e.writeBuf(this.content)}getRef(){return 3}},KQ=i=>new Tn(i.readBuf()),Rr=class i{constructor(e){this.len=e}getLength(){return this.len}getContent(){return[]}isCountable(){return!1}copy(){return new i(this.len)}splice(e){let t=new i(this.len-e);return this.len=e,t}mergeWith(e){return this.len+=e.len,!0}integrate(e,t){js(e.deleteSet,t.id.client,t.id.clock,this.len),t.markDeleted()}delete(e){}gc(e){}write(e,t){e.writeLen(this.len-t)}getRef(){return 1}},JQ=i=>new Rr(i.readLen()),mm=(i,e)=>new _i({guid:i,...e,shouldLoad:e.shouldLoad||e.autoLoad||!1}),An=class i{constructor(e){e._item&&console.error("This document was already integrated as a sub-document. You should create a second instance instead with the same guid."),this.doc=e;let t={};this.opts=t,e.gc||(t.gc=!1),e.autoLoad&&(t.autoLoad=!0),e.meta!==null&&(t.meta=e.meta)}getLength(){return 1}getContent(){return[this.doc]}isCountable(){return!0}copy(){return new i(mm(this.doc.guid,this.opts))}splice(e){throw pt()}mergeWith(e){return!1}integrate(e,t){this.doc._item=t,e.subdocsAdded.add(this.doc),this.doc.shouldLoad&&e.subdocsLoaded.add(this.doc)}delete(e){e.subdocsAdded.has(this.doc)?e.subdocsAdded.delete(this.doc):e.subdocsRemoved.add(this.doc)}gc(e){}write(e,t){e.writeString(this.doc.guid),e.writeAny(this.opts)}getRef(){return 9}},ev=i=>new An(mm(i.readString(),i.readAny())),mi=class i{constructor(e){this.embed=e}getLength(){return 1}getContent(){return[this.embed]}isCountable(){return!0}copy(){return new i(this.embed)}splice(e){throw pt()}mergeWith(e){return!1}integrate(e,t){}delete(e){}gc(e){}write(e,t){e.writeJSON(this.embed)}getRef(){return 5}},tv=i=>new mi(i.readJSON()),me=class i{constructor(e,t){this.key=e,this.value=t}getLength(){return 1}getContent(){return[]}isCountable(){return!1}copy(){return new i(this.key,this.value)}splice(e){throw pt()}mergeWith(e){return!1}integrate(e,t){let n=t.parent;n._searchMarker=null,n._hasFormatting=!0}delete(e){}gc(e){}write(e,t){e.writeKey(this.key),e.writeJSON(this.value)}getRef(){return 6}},iv=i=>new me(i.readKey(),i.readJSON()),Gs=class i{constructor(e){this.arr=e}getLength(){return this.arr.length}getContent(){return this.arr}isCountable(){return!0}copy(){return new i(this.arr)}splice(e){let t=new i(this.arr.slice(e));return this.arr=this.arr.slice(0,e),t}mergeWith(e){return this.arr=this.arr.concat(e.arr),!0}integrate(e,t){}delete(e){}gc(e){}write(e,t){let n=this.arr.length;e.writeLen(n-t);for(let r=t;r<n;r++){let s=this.arr[r];e.writeString(s===void 0?"undefined":JSON.stringify(s))}}getRef(){return 2}},nv=i=>{let e=i.readLen(),t=[];for(let n=0;n<e;n++){let r=i.readString();r==="undefined"?t.push(void 0):t.push(JSON.parse(r))}return new Gs(t)},rv=As("node_env")==="development",Di=class i{constructor(e){this.arr=e,rv&&kh(e)}getLength(){return this.arr.length}getContent(){return this.arr}isCountable(){return!0}copy(){return new i(this.arr)}splice(e){let t=new i(this.arr.slice(e));return this.arr=this.arr.slice(0,e),t}mergeWith(e){return this.arr=this.arr.concat(e.arr),!0}integrate(e,t){}delete(e){}gc(e){}write(e,t){let n=this.arr.length;e.writeLen(n-t);for(let r=t;r<n;r++){let s=this.arr[r];e.writeAny(s)}}getRef(){return 8}},sv=i=>{let e=i.readLen(),t=[];for(let n=0;n<e;n++)t.push(i.readAny());return new Di(t)},Dt=class i{constructor(e){this.str=e}getLength(){return this.str.length}getContent(){return this.str.split("")}isCountable(){return!0}copy(){return new i(this.str)}splice(e){let t=new i(this.str.slice(e));this.str=this.str.slice(0,e);let n=this.str.charCodeAt(e-1);return n>=55296&&n<=56319&&(this.str=this.str.slice(0,e-1)+"\uFFFD",t.str="\uFFFD"+t.str.slice(1)),t}mergeWith(e){return this.str+=e.str,!0}integrate(e,t){}delete(e){}gc(e){}write(e,t){e.writeString(t===0?this.str:this.str.slice(t))}getRef(){return 4}},ov=i=>new Dt(i.readString()),lv=[BQ,VQ,WQ,qQ,NQ,GQ,FQ],av=0,cv=1,hv=2,fv=3,uv=4,dv=5,pv=6,Pt=class i{constructor(e){this.type=e}getLength(){return 1}getContent(){return[this.type]}isCountable(){return!0}copy(){return new i(this.type._copy())}splice(e){throw pt()}mergeWith(e){return!1}integrate(e,t){this.type._integrate(e.doc,t)}delete(e){let t=this.type._start;for(;t!==null;)t.deleted?t.id.clock<(e.beforeState.get(t.id.client)||0)&&e._mergeStructs.push(t):t.delete(e),t=t.right;this.type._map.forEach(n=>{n.deleted?n.id.clock<(e.beforeState.get(n.id.client)||0)&&e._mergeStructs.push(n):n.delete(e)}),e.changed.delete(this.type)}gc(e){let t=this.type._start;for(;t!==null;)t.gc(e,!0),t=t.right;this.type._start=null,this.type._map.forEach(n=>{for(;n!==null;)n.gc(e,!0),n=n.left}),this.type._map=new Map}write(e,t){this.type._write(e)}getRef(){return 7}},Ov=i=>new Pt(lv[i.readTypeRef()](i)),tf=(i,e)=>{let t=e,n=0,r;do n>0&&(t=W(t.client,t.clock+n)),r=Sn(i,t),n=t.clock-r.id.clock,t=r.redone;while(t!==null&&r instanceof le);return{item:r,diff:n}},mf=(i,e)=>{for(;i!==null&&i.keep!==e;)i.keep=e,i=i.parent._item},Hl=(i,e,t)=>{let{client:n,clock:r}=e.id,s=new le(W(n,r+t),e,W(n,r+t-1),e.right,e.rightOrigin,e.parent,e.parentSub,e.content.splice(t));return e.deleted&&s.markDeleted(),e.keep&&(s.keep=!0),e.redone!==null&&(s.redone=W(e.redone.client,e.redone.clock+t)),e.right=s,s.right!==null&&(s.right.left=s),i._mergeStructs.push(s),s.parentSub!==null&&s.right===null&&s.parent._map.set(s.parentSub,s),e.length=t,s},Xg=(i,e)=>ys(i,t=>Mn(t.deletions,e)),bm=(i,e,t,n,r,s)=>{let o=i.doc,l=o.store,a=o.clientID,c=e.redone;if(c!==null)return Ue(i,c);let h=e.parent._item,f=null,u;if(h!==null&&h.deleted===!0){if(h.redone===null&&(!t.has(h)||bm(i,h,t,n,r,s)===null))return null;for(;h.redone!==null;)h=Ue(i,h.redone)}let d=h===null?e.parent:h.content.type;if(e.parentSub===null){for(f=e.left,u=e;f!==null;){let b=f;for(;b!==null&&b.parent._item!==h;)b=b.redone===null?null:Ue(i,b.redone);if(b!==null&&b.parent._item===h){f=b;break}f=f.left}for(;u!==null;){let b=u;for(;b!==null&&b.parent._item!==h;)b=b.redone===null?null:Ue(i,b.redone);if(b!==null&&b.parent._item===h){u=b;break}u=u.right}}else{if(u=null,e.right&&!r){for(f=e;f!==null&&f.right!==null&&(f.right.redone||Mn(n,f.right.id)||Xg(s.undoStack,f.right.id)||Xg(s.redoStack,f.right.id));)for(f=f.right;f.redone;)f=Ue(i,f.redone);if(f&&f.right!==null)return null}else f=d._map.get(e.parentSub)||null;f!==null&&f.parent._item!==h&&(f=d._map.get(e.parentSub)||null)}let p=ue(l,a),O=W(a,p),g=new le(O,f,f&&f.lastId,u,u&&u.id,d,e.parentSub,e.content.copy());return e.redone=O,mf(g,!0),g.integrate(i,0),g},le=class i extends Mr{constructor(e,t,n,r,s,o,l,a){super(e,a.getLength()),this.origin=n,this.left=t,this.right=r,this.rightOrigin=s,this.parent=o,this.parentSub=l,this.redone=null,this.content=a,this.info=this.content.isCountable()?2:0}set marker(e){(this.info&8)>0!==e&&(this.info^=8)}get marker(){return(this.info&8)>0}get keep(){return(this.info&1)>0}set keep(e){this.keep!==e&&(this.info^=1)}get countable(){return(this.info&2)>0}get deleted(){return(this.info&4)>0}set deleted(e){this.deleted!==e&&(this.info^=4)}markDeleted(){this.info|=4}getMissing(e,t){if(this.origin&&this.origin.client!==this.id.client&&this.origin.clock>=ue(t,this.origin.client))return this.origin.client;if(this.rightOrigin&&this.rightOrigin.client!==this.id.client&&this.rightOrigin.clock>=ue(t,this.rightOrigin.client))return this.rightOrigin.client;if(this.parent&&this.parent.constructor===Oi&&this.id.client!==this.parent.client&&this.parent.clock>=ue(t,this.parent.client))return this.parent.client;if(this.origin&&(this.left=Kh(e,t,this.origin),this.origin=this.left.lastId),this.rightOrigin&&(this.right=Ue(e,this.rightOrigin),this.rightOrigin=this.right.id),this.left&&this.left.constructor===Be||this.right&&this.right.constructor===Be)this.parent=null;else if(!this.parent)this.left&&this.left.constructor===i?(this.parent=this.left.parent,this.parentSub=this.left.parentSub):this.right&&this.right.constructor===i&&(this.parent=this.right.parent,this.parentSub=this.right.parentSub);else if(this.parent.constructor===Oi){let n=Sn(t,this.parent);n.constructor===Be?this.parent=null:this.parent=n.content.type}return null}integrate(e,t){if(t>0&&(this.id.clock+=t,this.left=Kh(e,e.doc.store,W(this.id.client,this.id.clock-1)),this.origin=this.left.lastId,this.content=this.content.splice(t),this.length-=t),this.parent){if(!this.left&&(!this.right||this.right.left!==null)||this.left&&this.left.right!==this.right){let n=this.left,r;if(n!==null)r=n.right;else if(this.parentSub!==null)for(r=this.parent._map.get(this.parentSub)||null;r!==null&&r.left!==null;)r=r.left;else r=this.parent._start;let s=new Set,o=new Set;for(;r!==null&&r!==this.right;){if(o.add(r),s.add(r),yn(this.origin,r.origin)){if(r.id.client<this.id.client)n=r,s.clear();else if(yn(this.rightOrigin,r.rightOrigin))break}else if(r.origin!==null&&o.has(Sn(e.doc.store,r.origin)))s.has(Sn(e.doc.store,r.origin))||(n=r,s.clear());else break;r=r.right}this.left=n}if(this.left!==null){let n=this.left.right;this.right=n,this.left.right=this}else{let n;if(this.parentSub!==null)for(n=this.parent._map.get(this.parentSub)||null;n!==null&&n.left!==null;)n=n.left;else n=this.parent._start,this.parent._start=this;this.right=n}this.right!==null?this.right.left=this:this.parentSub!==null&&(this.parent._map.set(this.parentSub,this),this.left!==null&&this.left.delete(e)),this.parentSub===null&&this.countable&&!this.deleted&&(this.parent._length+=this.length),Ug(e.doc.store,this),this.content.integrate(e,this),$g(e,this.parent,this.parentSub),(this.parent._item!==null&&this.parent._item.deleted||this.parentSub!==null&&this.right!==null)&&this.delete(e)}else new Be(this.id,this.length).integrate(e,0)}get next(){let e=this.right;for(;e!==null&&e.deleted;)e=e.right;return e}get prev(){let e=this.left;for(;e!==null&&e.deleted;)e=e.left;return e}get lastId(){return this.length===1?this.id:W(this.id.client,this.id.clock+this.length-1)}mergeWith(e){if(this.constructor===e.constructor&&yn(e.origin,this.lastId)&&this.right===e&&yn(this.rightOrigin,e.rightOrigin)&&this.id.client===e.id.client&&this.id.clock+this.length===e.id.clock&&this.deleted===e.deleted&&this.redone===null&&e.redone===null&&this.content.constructor===e.content.constructor&&this.content.mergeWith(e.content)){let t=this.parent._searchMarker;return t&&t.forEach(n=>{n.p===e&&(n.p=this,!this.deleted&&this.countable&&(n.index-=this.length))}),e.keep&&(this.keep=!0),this.right=e.right,this.right!==null&&(this.right.left=this),this.length+=e.length,!0}return!1}delete(e){if(!this.deleted){let t=this.parent;this.countable&&this.parentSub===null&&(t._length-=this.length),this.markDeleted(),js(e.deleteSet,this.id.client,this.id.clock,this.length),$g(e,t,this.parentSub),this.content.delete(e)}}gc(e,t){if(!this.deleted)throw Ce();this.content.gc(e),t?wQ(e,this,new Be(this.id,this.length)):this.content=new Rr(this.length)}write(e,t){let n=t>0?W(this.id.client,this.id.clock+t-1):this.origin,r=this.rightOrigin,s=this.parentSub,o=this.content.getRef()&31|(n===null?0:128)|(r===null?0:64)|(s===null?0:32);if(e.writeInfo(o),n!==null&&e.writeLeftID(n),r!==null&&e.writeRightID(r),n===null&&r===null){let l=this.parent;if(l._item!==void 0){let a=l._item;if(a===null){let c=hf(l);e.writeParentInfo(!0),e.writeString(c)}else e.writeParentInfo(!1),e.writeLeftID(a.id)}else l.constructor===String?(e.writeParentInfo(!0),e.writeString(l)):l.constructor===Oi?(e.writeParentInfo(!1),e.writeLeftID(l)):Ce();s!==null&&e.writeString(s)}this.content.write(e,t)}},ym=(i,e)=>gv[e&31](i),gv=[()=>{Ce()},JQ,nv,KQ,ov,tv,iv,Ov,sv,ev,()=>{Ce()}],mv=10,Xe=class extends Mr{get deleted(){return!0}delete(){}mergeWith(e){return this.constructor!==e.constructor?!1:(this.length+=e.length,!0)}integrate(e,t){Ce()}write(e,t){e.writeInfo(mv),B(e.restEncoder,this.length-t)}getMissing(e,t){return null}},Sm=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:{},xm="__ $YJS$ __";Sm[xm]===!0&&console.error("Yjs was already imported. This breaks constructor checks and will lead to issues! - https://github.com/yjs/yjs/issues/438");Sm[xm]=!0;var bf=3e4,yf=class extends hl{constructor(e){super(),this.doc=e,this.clientID=e.clientID,this.states=new Map,this.meta=new Map,this._checkInterval=setInterval(()=>{let t=pi();this.getLocalState()!==null&&bf/2<=t-this.meta.get(this.clientID).lastUpdated&&this.setLocalState(this.getLocalState());let n=[];this.meta.forEach((r,s)=>{s!==this.clientID&&bf<=t-r.lastUpdated&&this.states.has(s)&&n.push(s)}),n.length>0&&wm(this,n,"timeout")},Re(bf/10)),e.on("destroy",()=>{this.destroy()}),this.setLocalState({})}destroy(){this.emit("destroy",[this]),this.setLocalState(null),super.destroy(),clearInterval(this._checkInterval)}getLocalState(){return this.states.get(this.clientID)||null}setLocalState(e){let t=this.clientID,n=this.meta.get(t),r=n===void 0?0:n.clock+1,s=this.states.get(t);e===null?this.states.delete(t):this.states.set(t,e),this.meta.set(t,{clock:r,lastUpdated:pi()});let o=[],l=[],a=[],c=[];e===null?c.push(t):s==null?e!=null&&o.push(t):(l.push(t),Mi(s,e)||a.push(t)),(o.length>0||a.length>0||c.length>0)&&this.emit("change",[{added:o,updated:a,removed:c},"local"]),this.emit("update",[{added:o,updated:l,removed:c},"local"])}setLocalStateField(e,t){let n=this.getLocalState();n!==null&&this.setLocalState({...n,[e]:t})}getStates(){return this.states}},wm=(i,e,t)=>{let n=[];for(let r=0;r<e.length;r++){let s=e[r];if(i.states.has(s)){if(i.states.delete(s),s===i.clientID){let o=i.meta.get(s);i.meta.set(s,{clock:o.clock+1,lastUpdated:pi()})}n.push(s)}}n.length>0&&(i.emit("change",[{added:[],updated:[],removed:n},t]),i.emit("update",[{added:[],updated:[],removed:n},t]))},bv=(i,e,t=i.states)=>{let n=e.length,r=di();B(r,n);for(let s=0;s<n;s++){let o=e[s],l=t.get(o)||null,a=i.meta.get(o).clock;B(r,o),B(r,a),Kt(r,JSON.stringify(l))}return qe(r)};var yv=(i,e,t)=>{let n=Qe(e),r=pi(),s=[],o=[],l=[],a=[],c=z(n);for(let h=0;h<c;h++){let f=z(n),u=z(n),d=JSON.parse(Jt(n)),p=i.meta.get(f),O=i.states.get(f),g=p===void 0?0:p.clock;(g<u||g===u&&d===null&&i.states.has(f))&&(d===null?f===i.clientID&&i.getLocalState()!=null?u++:i.states.delete(f):i.states.set(f,d),i.meta.set(f,{clock:u,lastUpdated:r}),p===void 0&&d!==null?s.push(f):p!==void 0&&d===null?a.push(f):d!==null&&(Mi(d,O)||l.push(f),o.push(f)))}(s.length>0||l.length>0||a.length>0)&&i.emit("change",[{added:s,updated:l,removed:a},t]),(s.length>0||o.length>0||a.length>0)&&i.emit("update",[{added:s,updated:o,removed:a},t])};var xf=[],$m=[];(()=>{let i="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,1n,9,16,o,,x,1i,3,,i,,7,a,2,t,3,1k,,,7,2,2,2,3,9,,a,2,q,,2,3,1k,,,5,4,2,2,3,3,,u,2,3,,b,3,1k,,,8,,3,,3,k,2,m,6,,3,1k,,,7,2,2,2,3,7,3,a,2,u,,1n,5,3,3,,4,9,,14,5,1j,,,7,,3,,4,7,2,b,2,t,3,1k,,,7,,3,,4,7,2,b,2,f,,c,4,1j,2,,7,,3,,4,9,,a,2,t,3,1y,,4,6,,,,8,i,2,1p,,,8,c,8,2q,,,a,b,7,21,2,r,,,,,,4,2,1d,k,,2,5,b,,10,9,,2u,b,,6,n,4,4,3,g,4,d,,,3,6,,f,,jj,3,qa,4,s,3,t,2,u,2,1s,w,9,,19,3,,,39,2,y,,3a,c,4,c,63,5,1l,a,,,,,2,o,2,,1c,1a,2,c,k,5,1b,h,12,9,c,3,u,d,1k,e,1c,k,48,3,,l,4,,6,,2,3,5i,1s,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,n,5,4,,2b,2,1e,i,q,i,d,,12,8,p,d,18,4,1b,e,10,,1v,e,c,,8,2,1a,,1f,,,3,2,2,5,2,,,15,5,5,2,6k,8,,2,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,1t,5,8t,2,25,6,1y,b,1d,4,3e,3,1h,f,15,,2,2,a,4,19,b,7,,1p,3,10,e,g,2,18,,c,3,1c,e,8,4,,2,2k,c,6,,2,,4d,c,l,4,1j,2,,7,2,2,2,3,9,,a,2,2,7,3,5,1v,9,,,2,,,4,,5,,,e,2,2a,i,n,,29,k,6j,7,2,9,r,2,2a,h,2y,d,2t,3,2,a,74,f,6t,6,,2,2,4,,,,2,3x,7,2,7,3,,s,a,14,7,,4,8,,9,b,1a,g,5i,8,5j,8,,8,2a,m,,e,3e,6,3,,,2,,7,,,1u,5,,2,,5,9n,4,9,2,,,1c,7,3,5,n,,44l,,6,f,8ug,i,1xc,5,1n,7,t4,,,1j,7,4,29,,b,2,f57,2,3mp,1a,2,n,f2,5,3,6,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,2s,,4g,7,af,,1p,4,e4,4,72,2,6r,,2,,7,2,5,,d6,7,31,7,240,5".split(",").map(e=>e?parseInt(e,36):1);for(let e=0,t=0;e<i.length;e++)(e%2?$m:xf).push(t=t+i[e])})();function Sv(i){if(i<768)return!1;for(let e=0,t=xf.length;;){let n=e+t>>1;if(i<xf[n])t=n;else if(i>=$m[n])e=n+1;else return!0;if(e==t)return!1}}function km(i){return i>=127462&&i<=127487}var Qm=8205;function Pm(i,e,t=!0,n=!0){return(t?Cm:xv)(i,e,n)}function Cm(i,e,t){if(e==i.length)return e;e&&Tm(i.charCodeAt(e))&&Am(i.charCodeAt(e-1))&&e--;let n=Sf(i,e);for(e+=vm(n);e<i.length;){let r=Sf(i,e);if(n==Qm||r==Qm||t&&Sv(r))e+=vm(r),n=r;else if(km(r)){let s=0,o=e-2;for(;o>=0&&km(Sf(i,o));)s++,o-=2;if(s%2==0)break;e+=2}else break}return e}function xv(i,e,t){for(;e>1;){let n=Cm(i,e-2,t);if(n<e)return n;e--}return 0}function Sf(i,e){let t=i.charCodeAt(e);if(!Am(t)||e+1==i.length)return t;let n=i.charCodeAt(e+1);return Tm(n)?(t-55296<<10)+(n-56320)+65536:t}function Tm(i){return i>=56320&&i<57344}function Am(i){return i>=55296&&i<56320}function vm(i){return i<65536?1:2}var q=class i{lineAt(e){if(e<0||e>this.length)throw new RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw new RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,n){[e,t]=Ur(this,e,t);let r=[];return this.decompose(0,e,r,2),n.length&&n.decompose(0,n.length,r,3),this.decompose(t,this.length,r,1),Dr.from(r,this.length-(t-e)+n.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=Ur(this,e,t);let n=[];return this.decompose(e,t,n,0),Dr.from(n,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),n=this.length-this.scanIdentical(e,-1),r=new Zn(this),s=new Zn(e);for(let o=t,l=t;;){if(r.next(o),s.next(o),o=0,r.lineBreak!=s.lineBreak||r.done!=s.done||r.value!=s.value)return!1;if(l+=r.value.length,r.done||l>=n)return!0}}iter(e=1){return new Zn(this,e)}iterRange(e,t=this.length){return new ca(this,e,t)}iterLines(e,t){let n;if(e==null)n=this.iter();else{t==null&&(t=this.lines+1);let r=this.line(e).from;n=this.iterRange(r,Math.max(r,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new ha(n)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(e){if(e.length==0)throw new RangeError("A document must have at least one line");return e.length==1&&!e[0]?i.empty:e.length<=32?new gt(e):Dr.from(gt.split(e,[]))}},gt=class i extends q{constructor(e,t=wv(e)){super(),this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,n,r){for(let s=0;;s++){let o=this.text[s],l=r+o.length;if((t?n:l)>=e)return new kf(r,l,n,o);r=l+1,n++}}decompose(e,t,n,r){let s=e<=0&&t>=this.length?this:new i(Mm(this.text,e,t),Math.min(t,this.length)-Math.max(0,e));if(r&1){let o=n.pop(),l=aa(s.text,o.text.slice(),0,s.length);if(l.length<=32)n.push(new i(l,o.length+s.length));else{let a=l.length>>1;n.push(new i(l.slice(0,a)),new i(l.slice(a)))}}else n.push(s)}replace(e,t,n){if(!(n instanceof i))return super.replace(e,t,n);[e,t]=Ur(this,e,t);let r=aa(this.text,aa(n.text,Mm(this.text,0,e)),t),s=this.length+n.length-(t-e);return r.length<=32?new i(r,s):Dr.from(i.split(r,[]),s)}sliceString(e,t=this.length,n=` +`){[e,t]=Ur(this,e,t);let r="";for(let s=0,o=0;s<=t&&o<this.text.length;o++){let l=this.text[o],a=s+l.length;s>e&&o&&(r+=n),e<a&&t>s&&(r+=l.slice(Math.max(0,e-s),t-s)),s=a+1}return r}flatten(e){for(let t of this.text)e.push(t)}scanIdentical(){return 0}static split(e,t){let n=[],r=-1;for(let s of e)n.push(s),r+=s.length+1,n.length==32&&(t.push(new i(n,r)),n=[],r=-1);return r>-1&&t.push(new i(n,r)),t}},Dr=class i extends q{constructor(e,t){super(),this.children=e,this.length=t,this.lines=0;for(let n of e)this.lines+=n.lines}lineInner(e,t,n,r){for(let s=0;;s++){let o=this.children[s],l=r+o.length,a=n+o.lines-1;if((t?a:l)>=e)return o.lineInner(e,t,n,r);r=l+1,n=a+1}}decompose(e,t,n,r){for(let s=0,o=0;o<=t&&s<this.children.length;s++){let l=this.children[s],a=o+l.length;if(e<=a&&t>=o){let c=r&((o<=e?1:0)|(a>=t?2:0));o>=e&&a<=t&&!c?n.push(l):l.decompose(e-o,t-o,n,c)}o=a+1}}replace(e,t,n){if([e,t]=Ur(this,e,t),n.lines<this.lines)for(let r=0,s=0;r<this.children.length;r++){let o=this.children[r],l=s+o.length;if(e>=s&&t<=l){let a=o.replace(e-s,t-s,n),c=this.lines-o.lines+a.lines;if(a.lines<c>>4&&a.lines>c>>6){let h=this.children.slice();return h[r]=a,new i(h,this.length-(t-e)+n.length)}return super.replace(s,l,a)}s=l+1}return super.replace(e,t,n)}sliceString(e,t=this.length,n=` +`){[e,t]=Ur(this,e,t);let r="";for(let s=0,o=0;s<this.children.length&&o<=t;s++){let l=this.children[s],a=o+l.length;o>e&&s&&(r+=n),e<a&&t>o&&(r+=l.sliceString(e-o,t-o,n)),o=a+1}return r}flatten(e){for(let t of this.children)t.flatten(e)}scanIdentical(e,t){if(!(e instanceof i))return 0;let n=0,[r,s,o,l]=t>0?[0,0,this.children.length,e.children.length]:[this.children.length-1,e.children.length-1,-1,-1];for(;;r+=t,s+=t){if(r==o||s==l)return n;let a=this.children[r],c=e.children[s];if(a!=c)return n+a.scanIdentical(c,t);n+=a.length+1}}static from(e,t=e.reduce((n,r)=>n+r.length+1,-1)){let n=0;for(let d of e)n+=d.lines;if(n<32){let d=[];for(let p of e)p.flatten(d);return new gt(d,t)}let r=Math.max(32,n>>5),s=r<<1,o=r>>1,l=[],a=0,c=-1,h=[];function f(d){let p;if(d.lines>s&&d instanceof i)for(let O of d.children)f(O);else d.lines>o&&(a>o||!a)?(u(),l.push(d)):d instanceof gt&&a&&(p=h[h.length-1])instanceof gt&&d.lines+p.lines<=32?(a+=d.lines,c+=d.length+1,h[h.length-1]=new gt(p.text.concat(d.text),p.length+1+d.length)):(a+d.lines>r&&u(),a+=d.lines,c+=d.length+1,h.push(d))}function u(){a!=0&&(l.push(h.length==1?h[0]:i.from(h,c)),c=-1,a=h.length=0)}for(let d of e)f(d);return u(),l.length==1?l[0]:new i(l,t)}};q.empty=new gt([""],0);function wv(i){let e=-1;for(let t of i)e+=t.length+1;return e}function aa(i,e,t=0,n=1e9){for(let r=0,s=0,o=!0;s<i.length&&r<=n;s++){let l=i[s],a=r+l.length;a>=t&&(a>n&&(l=l.slice(0,n-r)),r<t&&(l=l.slice(t-r)),o?(e[e.length-1]+=l,o=!1):e.push(l)),r=a+1}return e}function Mm(i,e,t){return aa(i,[""],e,t)}var Zn=class{constructor(e,t=1){this.dir=t,this.done=!1,this.lineBreak=!1,this.value="",this.nodes=[e],this.offsets=[t>0?1:(e instanceof gt?e.text.length:e.children.length)<<1]}nextInner(e,t){for(this.done=this.lineBreak=!1;;){let n=this.nodes.length-1,r=this.nodes[n],s=this.offsets[n],o=s>>1,l=r instanceof gt?r.text.length:r.children.length;if(o==(t>0?l:0)){if(n==0)return this.done=!0,this.value="",this;t>0&&this.offsets[n-1]++,this.nodes.pop(),this.offsets.pop()}else if((s&1)==(t>0?0:1)){if(this.offsets[n]+=t,e==0)return this.lineBreak=!0,this.value=` +`,this;e--}else if(r instanceof gt){let a=r.text[o+(t<0?-1:0)];if(this.offsets[n]+=t,a.length>Math.max(0,e))return this.value=e==0?a:t>0?a.slice(e):a.slice(0,a.length-e),this;e-=a.length}else{let a=r.children[o+(t<0?-1:0)];e>a.length?(e-=a.length,this.offsets[n]+=t):(t<0&&this.offsets[n]--,this.nodes.push(a),this.offsets.push(t>0?1:(a instanceof gt?a.text.length:a.children.length)<<1))}}}next(e=0){return e<0&&(this.nextInner(-e,-this.dir),e=this.value.length),this.nextInner(e,this.dir)}},ca=class{constructor(e,t,n){this.value="",this.done=!1,this.cursor=new Zn(e,t>n?-1:1),this.pos=t>n?e.length:0,this.from=Math.min(t,n),this.to=Math.max(t,n)}nextInner(e,t){if(t<0?this.pos<=this.from:this.pos>=this.to)return this.value="",this.done=!0,this;e+=Math.max(0,t<0?this.pos-this.to:this.from-this.pos);let n=t<0?this.pos-this.from:this.to-this.pos;e>n&&(e=n),n-=e;let{value:r}=this.cursor.next(e);return this.pos+=(r.length+e)*t,this.value=r.length<=n?r:t<0?r.slice(r.length-n):r.slice(0,n),this.done=!this.value,this}next(e=0){return e<0?e=Math.max(e,this.from-this.pos):e>0&&(e=Math.min(e,this.to-this.pos)),this.nextInner(e,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&this.value!=""}},ha=class{constructor(e){this.inner=e,this.afterBreak=!0,this.value="",this.done=!1}next(e=0){let{done:t,lineBreak:n,value:r}=this.inner.next(e);return t&&this.afterBreak?(this.value="",this.afterBreak=!1):t?(this.done=!0,this.value=""):n?this.afterBreak?this.value="":(this.afterBreak=!0,this.next()):(this.value=r,this.afterBreak=!1),this}get lineBreak(){return!1}};typeof Symbol<"u"&&(q.prototype[Symbol.iterator]=function(){return this.iter()},Zn.prototype[Symbol.iterator]=ca.prototype[Symbol.iterator]=ha.prototype[Symbol.iterator]=function(){return this});var kf=class{constructor(e,t,n,r){this.from=e,this.to=t,this.number=n,this.text=r}get length(){return this.to-this.from}};function Ur(i,e,t){return e=Math.max(0,Math.min(i.length,e)),[e,Math.max(e,Math.min(i.length,t))]}function be(i,e,t=!0,n=!0){return Pm(i,e,t,n)}function kv(i){return i>=56320&&i<57344}function Qv(i){return i>=55296&&i<56320}function Ze(i,e){let t=i.charCodeAt(e);if(!Qv(t)||e+1==i.length)return t;let n=i.charCodeAt(e+1);return kv(n)?(t-55296<<10)+(n-56320)+65536:t}function io(i){return i<=65535?String.fromCharCode(i):(i-=65536,String.fromCharCode((i>>10)+55296,(i&1023)+56320))}function mt(i){return i<65536?1:2}var Qf=/\r\n?|\n/,$e=(function(i){return i[i.Simple=0]="Simple",i[i.TrackDel=1]="TrackDel",i[i.TrackBefore=2]="TrackBefore",i[i.TrackAfter=3]="TrackAfter",i})($e||($e={})),yi=class i{constructor(e){this.sections=e}get length(){let e=0;for(let t=0;t<this.sections.length;t+=2)e+=this.sections[t];return e}get newLength(){let e=0;for(let t=0;t<this.sections.length;t+=2){let n=this.sections[t+1];e+=n<0?this.sections[t]:n}return e}get empty(){return this.sections.length==0||this.sections.length==2&&this.sections[1]<0}iterGaps(e){for(let t=0,n=0,r=0;t<this.sections.length;){let s=this.sections[t++],o=this.sections[t++];o<0?(e(n,r,s),r+=s):r+=o,n+=s}}iterChangedRanges(e,t=!1){vf(this,e,t)}get invertedDesc(){let e=[];for(let t=0;t<this.sections.length;){let n=this.sections[t++],r=this.sections[t++];r<0?e.push(n,r):e.push(r,n)}return new i(e)}composeDesc(e){return this.empty?e:e.empty?this:_m(this,e)}mapDesc(e,t=!1){return e.empty?this:$f(this,e,t)}mapPos(e,t=-1,n=$e.Simple){let r=0,s=0;for(let o=0;o<this.sections.length;){let l=this.sections[o++],a=this.sections[o++],c=r+l;if(a<0){if(c>e)return s+(e-r);s+=l}else{if(n!=$e.Simple&&c>=e&&(n==$e.TrackDel&&r<e&&c>e||n==$e.TrackBefore&&r<e||n==$e.TrackAfter&&c>e))return null;if(c>e||c==e&&t<0&&!l)return e==r||t<0?s:s+a;s+=a}r=c}if(e>r)throw new RangeError(`Position ${e} is out of range for changeset of length ${r}`);return s}touchesRange(e,t=e){for(let n=0,r=0;n<this.sections.length&&r<=t;){let s=this.sections[n++],o=this.sections[n++],l=r+s;if(o>=0&&r<=t&&l>=e)return r<e&&l>t?"cover":!0;r=l}return!1}toString(){let e="";for(let t=0;t<this.sections.length;){let n=this.sections[t++],r=this.sections[t++];e+=(e?" ":"")+n+(r>=0?":"+r:"")}return e}toJSON(){return this.sections}static fromJSON(e){if(!Array.isArray(e)||e.length%2||e.some(t=>typeof t!="number"))throw new RangeError("Invalid JSON representation of ChangeDesc");return new i(e)}static create(e){return new i(e)}},He=class i extends yi{constructor(e,t){super(e),this.inserted=t}apply(e){if(this.length!=e.length)throw new RangeError("Applying change set to a document with the wrong length");return vf(this,(t,n,r,s,o)=>e=e.replace(r,r+(n-t),o),!1),e}mapDesc(e,t=!1){return $f(this,e,t,!0)}invert(e){let t=this.sections.slice(),n=[];for(let r=0,s=0;r<t.length;r+=2){let o=t[r],l=t[r+1];if(l>=0){t[r]=l,t[r+1]=o;let a=r>>1;for(;n.length<a;)n.push(q.empty);n.push(o?e.slice(s,s+o):q.empty)}s+=o}return new i(t,n)}compose(e){return this.empty?e:e.empty?this:_m(this,e,!0)}map(e,t=!1){return e.empty?this:$f(this,e,t,!0)}iterChanges(e,t=!1){vf(this,e,t)}get desc(){return yi.create(this.sections)}filter(e){let t=[],n=[],r=[],s=new _n(this);e:for(let o=0,l=0;;){let a=o==e.length?1e9:e[o++];for(;l<a||l==a&&s.len==0;){if(s.done)break e;let h=Math.min(s.len,a-l);Ve(r,h,-1);let f=s.ins==-1?-1:s.off==0?s.ins:0;Ve(t,h,f),f>0&&ji(n,t,s.text),s.forward(h),l+=h}let c=e[o++];for(;l<c;){if(s.done)break e;let h=Math.min(s.len,c-l);Ve(t,h,-1),Ve(r,h,s.ins==-1?-1:s.off==0?s.ins:0),s.forward(h),l+=h}}return{changes:new i(t,n),filtered:yi.create(r)}}toJSON(){let e=[];for(let t=0;t<this.sections.length;t+=2){let n=this.sections[t],r=this.sections[t+1];r<0?e.push(n):r==0?e.push([n]):e.push([n].concat(this.inserted[t>>1].toJSON()))}return e}static of(e,t,n){let r=[],s=[],o=0,l=null;function a(h=!1){if(!h&&!r.length)return;o<t&&Ve(r,t-o,-1);let f=new i(r,s);l=l?l.compose(f.map(l)):f,r=[],s=[],o=0}function c(h){if(Array.isArray(h))for(let f of h)c(f);else if(h instanceof i){if(h.length!=t)throw new RangeError(`Mismatched change set length (got ${h.length}, expected ${t})`);a(),l=l?l.compose(h.map(l)):h}else{let{from:f,to:u=f,insert:d}=h;if(f>u||f<0||u>t)throw new RangeError(`Invalid change range ${f} to ${u} (in doc of length ${t})`);let p=d?typeof d=="string"?q.of(d.split(n||Qf)):d:q.empty,O=p.length;if(f==u&&O==0)return;f<o&&a(),f>o&&Ve(r,f-o,-1),Ve(r,u-f,O),ji(s,r,p),o=u}}return c(e),a(!l),l}static empty(e){return new i(e?[e,-1]:[],[])}static fromJSON(e){if(!Array.isArray(e))throw new RangeError("Invalid JSON representation of ChangeSet");let t=[],n=[];for(let r=0;r<e.length;r++){let s=e[r];if(typeof s=="number")t.push(s,-1);else{if(!Array.isArray(s)||typeof s[0]!="number"||s.some((o,l)=>l&&typeof o!="string"))throw new RangeError("Invalid JSON representation of ChangeSet");if(s.length==1)t.push(s[0],0);else{for(;n.length<r;)n.push(q.empty);n[r]=q.of(s.slice(1)),t.push(s[0],n[r].length)}}}return new i(t,n)}static createSet(e,t){return new i(e,t)}};function Ve(i,e,t,n=!1){if(e==0&&t<=0)return;let r=i.length-2;r>=0&&t<=0&&t==i[r+1]?i[r]+=e:r>=0&&e==0&&i[r]==0?i[r+1]+=t:n?(i[r]+=e,i[r+1]+=t):i.push(e,t)}function ji(i,e,t){if(t.length==0)return;let n=e.length-2>>1;if(n<i.length)i[i.length-1]=i[i.length-1].append(t);else{for(;i.length<n;)i.push(q.empty);i.push(t)}}function vf(i,e,t){let n=i.inserted;for(let r=0,s=0,o=0;o<i.sections.length;){let l=i.sections[o++],a=i.sections[o++];if(a<0)r+=l,s+=l;else{let c=r,h=s,f=q.empty;for(;c+=l,h+=a,a&&n&&(f=f.append(n[o-2>>1])),!(t||o==i.sections.length||i.sections[o+1]<0);)l=i.sections[o++],a=i.sections[o++];e(r,c,s,h,f),r=c,s=h}}}function $f(i,e,t,n=!1){let r=[],s=n?[]:null,o=new _n(i),l=new _n(e);for(let a=-1;;){if(o.done&&l.len||l.done&&o.len)throw new Error("Mismatched change set lengths");if(o.ins==-1&&l.ins==-1){let c=Math.min(o.len,l.len);Ve(r,c,-1),o.forward(c),l.forward(c)}else if(l.ins>=0&&(o.ins<0||a==o.i||o.off==0&&(l.len<o.len||l.len==o.len&&!t))){let c=l.len;for(Ve(r,l.ins,-1);c;){let h=Math.min(o.len,c);o.ins>=0&&a<o.i&&o.len<=h&&(Ve(r,0,o.ins),s&&ji(s,r,o.text),a=o.i),o.forward(h),c-=h}l.next()}else if(o.ins>=0){let c=0,h=o.len;for(;h;)if(l.ins==-1){let f=Math.min(h,l.len);c+=f,h-=f,l.forward(f)}else if(l.ins==0&&l.len<h)h-=l.len,l.next();else break;Ve(r,c,a<o.i?o.ins:0),s&&a<o.i&&ji(s,r,o.text),a=o.i,o.forward(o.len-h)}else{if(o.done&&l.done)return s?He.createSet(r,s):yi.create(r);throw new Error("Mismatched change set lengths")}}}function _m(i,e,t=!1){let n=[],r=t?[]:null,s=new _n(i),o=new _n(e);for(let l=!1;;){if(s.done&&o.done)return r?He.createSet(n,r):yi.create(n);if(s.ins==0)Ve(n,s.len,0,l),s.next();else if(o.len==0&&!o.done)Ve(n,0,o.ins,l),r&&ji(r,n,o.text),o.next();else{if(s.done||o.done)throw new Error("Mismatched change set lengths");{let a=Math.min(s.len2,o.len),c=n.length;if(s.ins==-1){let h=o.ins==-1?-1:o.off?0:o.ins;Ve(n,a,h,l),r&&h&&ji(r,n,o.text)}else o.ins==-1?(Ve(n,s.off?0:s.len,a,l),r&&ji(r,n,s.textBit(a))):(Ve(n,s.off?0:s.len,o.off?0:o.ins,l),r&&!o.off&&ji(r,n,o.text));l=(s.ins>a||o.ins>=0&&o.len>a)&&(l||n.length>c),s.forward2(a),o.forward(a)}}}}var _n=class{constructor(e){this.set=e,this.i=0,this.next()}next(){let{sections:e}=this.set;this.i<e.length?(this.len=e[this.i++],this.ins=e[this.i++]):(this.len=0,this.ins=-2),this.off=0}get done(){return this.ins==-2}get len2(){return this.ins<0?this.len:this.ins}get text(){let{inserted:e}=this.set,t=this.i-2>>1;return t>=e.length?q.empty:e[t]}textBit(e){let{inserted:t}=this.set,n=this.i-2>>1;return n>=t.length&&!e?q.empty:t[n].slice(this.off,e==null?void 0:this.off+e)}forward(e){e==this.len?this.next():(this.len-=e,this.off+=e)}forward2(e){this.ins==-1?this.forward(e):e==this.ins?this.next():(this.ins-=e,this.off+=e)}},Rn=class i{constructor(e,t,n,r){this.from=e,this.to=t,this.flags=n,this.goalColumn=r}get anchor(){return this.flags&32?this.to:this.from}get head(){return this.flags&32?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return this.flags&8?-1:this.flags&16?1:0}get undirectional(){return(this.flags&64)>0}get bidiLevel(){let e=this.flags&7;return e==7?null:e}map(e,t=-1){let n,r;return this.empty?n=r=e.mapPos(this.from,t):(n=e.mapPos(this.from,1),r=e.mapPos(this.to,-1)),n==this.from&&r==this.to?this:new i(n,r,this.flags,this.goalColumn)}extend(e,t=e,n=0){if(e<=this.anchor&&t>=this.anchor)return v.range(e,t,void 0,void 0,n);let r=Math.abs(e-this.anchor)>Math.abs(t-this.anchor)?e:t;return v.range(this.anchor,r,void 0,void 0,n)}eq(e,t=!1){return this.anchor==e.anchor&&this.head==e.head&&this.goalColumn==e.goalColumn&&(!t||!this.empty||this.assoc==e.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(e){if(!e||typeof e.anchor!="number"||typeof e.head!="number")throw new RangeError("Invalid JSON representation for SelectionRange");return v.range(e.anchor,e.head)}static create(e,t,n,r){return new i(e,t,n,r)}},v=class i{constructor(e,t){this.ranges=e,this.mainIndex=t}map(e,t=-1){return e.empty?this:i.create(this.ranges.map(n=>n.map(e,t)),this.mainIndex)}eq(e,t=!1){if(this.ranges.length!=e.ranges.length||this.mainIndex!=e.mainIndex)return!1;for(let n=0;n<this.ranges.length;n++)if(!this.ranges[n].eq(e.ranges[n],t))return!1;return!0}get main(){return this.ranges[this.mainIndex]}asSingle(){return this.ranges.length==1?this:new i([this.main],0)}addRange(e,t=!0){return i.create([e].concat(this.ranges),t?0:this.mainIndex+1)}replaceRange(e,t=this.mainIndex){let n=this.ranges.slice();return n[t]=e,i.create(n,this.mainIndex)}toJSON(){return{ranges:this.ranges.map(e=>e.toJSON()),main:this.mainIndex}}static fromJSON(e){if(!e||!Array.isArray(e.ranges)||typeof e.main!="number"||e.main>=e.ranges.length)throw new RangeError("Invalid JSON representation for EditorSelection");return new i(e.ranges.map(t=>Rn.fromJSON(t)),e.main)}static single(e,t=e){return new i([i.range(e,t)],0)}static create(e,t=0){if(e.length==0)throw new RangeError("A selection needs at least one range");for(let n=0,r=0;r<e.length;r++){let s=e[r];if(s.empty?s.from<=n:s.from<n)return i.normalized(e.slice(),t);n=s.to}return new i(e,t)}static cursor(e,t=0,n,r){return Rn.create(e,e,(t==0?0:t<0?8:16)|(n==null?7:Math.min(6,n)),r)}static range(e,t,n,r,s){let o=r==null?7:Math.min(6,r);return!s&&e!=t&&(s=t<e?1:-1),s&&(o|=s<0?8:16),t<e?Rn.create(t,e,o|32,n):Rn.create(e,t,o,n)}static undirectionalRange(e,t){return Rn.create(e,t,64,void 0)}static normalized(e,t=0){let n=e[t];e.sort((r,s)=>r.from-s.from),t=e.indexOf(n);for(let r=1;r<e.length;r++){let s=e[r],o=e[r-1];if(s.empty?s.from<=o.to:s.from<o.to){let l=o.from,a=Math.max(s.to,o.to);r<=t&&t--,e.splice(--r,2,s.anchor>s.head?i.range(a,l):i.range(l,a))}}return new i(e,t)}};function Lm(i,e){for(let t of i.ranges)if(t.to>e)throw new RangeError("Selection points outside of document")}var _f=0,R=class i{constructor(e,t,n,r,s){this.combine=e,this.compareInput=t,this.compare=n,this.isStatic=r,this.id=_f++,this.default=e([]),this.extensions=typeof s=="function"?s(this):s}get reader(){return this}static define(e={}){return new i(e.combine||(t=>t),e.compareInput||((t,n)=>t===n),e.compare||(e.combine?(t,n)=>t===n:Lf),!!e.static,e.enables)}of(e){return new jr([],this,0,e)}compute(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new jr(e,this,1,t)}computeN(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new jr(e,this,2,t)}from(e,t){return t||(t=n=>n),this.compute([e],n=>t(n.field(e)))}};function Lf(i,e){return i==e||i.length==e.length&&i.every((t,n)=>t===e[n])}var jr=class{constructor(e,t,n,r){this.dependencies=e,this.facet=t,this.type=n,this.value=r,this.id=_f++}dynamicSlot(e){var t;let n=this.value,r=this.facet.compareInput,s=this.id,o=e[s]>>1,l=this.type==2,a=!1,c=!1,h=[];for(let f of this.dependencies)f=="doc"?a=!0:f=="selection"?c=!0:(((t=e[f.id])!==null&&t!==void 0?t:1)&1)==0&&h.push(e[f.id]);return{create(f){return f.values[o]=n(f),1},update(f,u){if(a&&u.docChanged||c&&(u.docChanged||u.selection)||Pf(f,h)){let d=n(f);if(l?!Rm(d,f.values[o],r):!r(d,f.values[o]))return f.values[o]=d,1}return 0},reconfigure:(f,u)=>{let d,p=u.config.address[s];if(p!=null){let O=pa(u,p);if(this.dependencies.every(g=>g instanceof R?u.facet(g)===f.facet(g):g instanceof de?u.field(g,!1)==f.field(g,!1):!0)||(l?Rm(d=n(f),O,r):r(d=n(f),O)))return f.values[o]=O,0}else d=n(f);return f.values[o]=d,1}}}get extension(){return this}};function Rm(i,e,t){if(i.length!=e.length)return!1;for(let n=0;n<i.length;n++)if(!t(i[n],e[n]))return!1;return!0}function Pf(i,e){let t=!1;for(let n of e)Ks(i,n)&1&&(t=!0);return t}function vv(i,e,t){let n=t.map(a=>i[a.id]),r=t.map(a=>a.type),s=n.filter(a=>!(a&1)),o=i[e.id]>>1;function l(a){let c=[];for(let h=0;h<n.length;h++){let f=pa(a,n[h]);if(r[h]==2)for(let u of f)c.push(u);else c.push(f)}return e.combine(c)}return{create(a){for(let c of n)Ks(a,c);return a.values[o]=l(a),1},update(a,c){if(!Pf(a,s))return 0;let h=l(a);return e.compare(h,a.values[o])?0:(a.values[o]=h,1)},reconfigure(a,c){let h=Pf(a,n),f=c.config.facets[e.id],u=c.facet(e);if(f&&!h&&Lf(t,f))return a.values[o]=u,0;let d=l(a);return e.compare(d,u)?(a.values[o]=u,0):(a.values[o]=d,1)}}}var sa=R.define({static:!0}),de=class i{constructor(e,t,n,r,s){this.id=e,this.createF=t,this.updateF=n,this.compareF=r,this.spec=s,this.provides=void 0}static define(e){let t=new i(_f++,e.create,e.update,e.compare||((n,r)=>n===r),e);return e.provide&&(t.provides=e.provide(t)),t}create(e){let t=e.facet(sa).find(n=>n.field==this);return(t?.create||this.createF)(e)}slot(e){let t=e[this.id]>>1;return{create:n=>(n.values[t]=this.create(n),1),update:(n,r)=>{let s=n.values[t],o=this.updateF(s,r);return this.compareF(s,o)?0:(n.values[t]=o,1)},reconfigure:(n,r)=>{let s=n.facet(sa),o=r.facet(sa),l;return(l=s.find(a=>a.field==this))&&l!=o.find(a=>a.field==this)?(n.values[t]=l.create(n),1):r.config.address[this.id]!=null?(n.values[t]=r.field(this),0):(n.values[t]=this.create(n),1)}}}init(e){return[this,sa.of({field:this,create:e})]}get extension(){return this}},Xn={lowest:4,low:3,default:2,high:1,highest:0};function Hs(i){return e=>new fa(e,i)}var at={highest:Hs(Xn.highest),high:Hs(Xn.high),default:Hs(Xn.default),low:Hs(Xn.low),lowest:Hs(Xn.lowest)},fa=class{constructor(e,t){this.inner=e,this.prec=t}get extension(){return this}},ua=class i{of(e){return new Js(this,e)}reconfigure(e){return i.reconfigure.of({compartment:this,extension:e})}get(e){return e.config.compartments.get(this)}},Js=class{constructor(e,t){this.compartment=e,this.inner=t}get extension(){return this}},da=class i{constructor(e,t,n,r,s,o){for(this.base=e,this.compartments=t,this.dynamicSlots=n,this.address=r,this.staticValues=s,this.facets=o,this.statusTemplate=[];this.statusTemplate.length<n.length;)this.statusTemplate.push(0)}staticFacet(e){let t=this.address[e.id];return t==null?e.default:this.staticValues[t>>1]}static resolve(e,t,n){let r=[],s=Object.create(null),o=new Map;for(let u of $v(e,t,o))u instanceof de?r.push(u):(s[u.facet.id]||(s[u.facet.id]=[])).push(u);let l=Object.create(null),a=[],c=[];for(let u of r)l[u.id]=c.length<<1,c.push(d=>u.slot(d));let h=n?.config.facets;for(let u in s){let d=s[u],p=d[0].facet,O=h&&h[u]||[];if(d.every(g=>g.type==0))if(l[p.id]=a.length<<1|1,Lf(O,d))a.push(n.facet(p));else{let g=p.combine(d.map(b=>b.value));a.push(n&&p.compare(g,n.facet(p))?n.facet(p):g)}else{for(let g of d)g.type==0?(l[g.id]=a.length<<1|1,a.push(g.value)):(l[g.id]=c.length<<1,c.push(b=>g.dynamicSlot(b)));l[p.id]=c.length<<1,c.push(g=>vv(g,p,d))}}let f=c.map(u=>u(l));return new i(e,o,f,l,a,s)}};function $v(i,e,t){let n=[[],[],[],[],[]],r=new Map;function s(o,l){let a=r.get(o);if(a!=null){if(a<=l)return;let c=n[a].indexOf(o);c>-1&&n[a].splice(c,1),o instanceof Js&&t.delete(o.compartment)}if(r.set(o,l),Array.isArray(o))for(let c of o)s(c,l);else if(o instanceof Js){if(t.has(o.compartment))throw new RangeError("Duplicate use of compartment in extensions");let c=e.get(o.compartment)||o.inner;t.set(o.compartment,c),s(c,l)}else if(o instanceof fa)s(o.inner,o.prec);else if(o instanceof de)n[l].push(o),o.provides&&s(o.provides,l);else if(o instanceof jr)n[l].push(o),o.facet.extensions&&s(o.facet.extensions,Xn.default);else{let c=o.extension;if(!c)throw new Error(`Unrecognized extension value in extension set (${o}).`);if(c==o)throw new Error(`Unrecognized extension value in extension set (${o}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);s(c,l)}}return s(i,Xn.default),n.reduce((o,l)=>o.concat(l))}function Ks(i,e){if(e&1)return 2;let t=e>>1,n=i.status[t];if(n==4)throw new Error("Cyclic dependency between fields and/or facets");if(n&2)return n;i.status[t]=4;let r=i.computeSlot(i,i.config.dynamicSlots[t]);return i.status[t]=2|r}function pa(i,e){return e&1?i.config.staticValues[e>>1]:i.values[e>>1]}var Dm=R.define(),Cf=R.define({combine:i=>i.some(e=>e),static:!0}),jm=R.define({combine:i=>i.length?i[0]:void 0,static:!0}),Im=R.define(),Um=R.define(),Bm=R.define(),Vm=R.define({combine:i=>i.length?i[0]:!1}),we=class{constructor(e,t){this.type=e,this.value=t}static define(){return new Tf}},Tf=class{of(e){return new we(this,e)}},Af=class{constructor(e){this.map=e}of(e){return new Y(this,e)}},Y=class i{constructor(e,t){this.type=e,this.value=t}map(e){let t=this.type.map(this.value,e);return t===void 0?void 0:t==this.value?this:new i(this.type,t)}is(e){return this.type==e}static define(e={}){return new Af(e.map||(t=>t))}static mapEffects(e,t){if(!e.length)return e;let n=[];for(let r of e){let s=r.map(t);s&&n.push(s)}return n}};Y.reconfigure=Y.define();Y.appendConfig=Y.define();var xe=class i{constructor(e,t,n,r,s,o){this.startState=e,this.changes=t,this.selection=n,this.effects=r,this.annotations=s,this.scrollIntoView=o,this._doc=null,this._state=null,n&&Lm(n,t.newLength),s.some(l=>l.type==i.time)||(this.annotations=s.concat(i.time.of(Date.now())))}static create(e,t,n,r,s,o){return new i(e,t,n,r,s,o)}get newDoc(){return this._doc||(this._doc=this.changes.apply(this.startState.doc))}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(e){for(let t of this.annotations)if(t.type==e)return t.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(e){let t=this.annotation(i.userEvent);return!!(t&&(t==e||t.length>e.length&&t.slice(0,e.length)==e&&t[e.length]=="."))}};xe.time=we.define();xe.userEvent=we.define();xe.addToHistory=we.define();xe.remote=we.define();function Pv(i,e){let t=[];for(let n=0,r=0;;){let s,o;if(n<i.length&&(r==e.length||e[r]>=i[n]))s=i[n++],o=i[n++];else if(r<e.length)s=e[r++],o=e[r++];else return t;!t.length||t[t.length-1]<s?t.push(s,o):t[t.length-1]<o&&(t[t.length-1]=o)}}function Ym(i,e,t){var n;let r,s,o;return t?(r=e.changes,s=He.empty(e.changes.length),o=i.changes.compose(e.changes)):(r=e.changes.map(i.changes),s=i.changes.mapDesc(e.changes,!0),o=i.changes.compose(r)),{changes:o,selection:e.selection?e.selection.map(s):(n=i.selection)===null||n===void 0?void 0:n.map(r),effects:Y.mapEffects(i.effects,r).concat(Y.mapEffects(e.effects,s)),annotations:i.annotations.length?i.annotations.concat(e.annotations):e.annotations,scrollIntoView:i.scrollIntoView||e.scrollIntoView}}function Mf(i,e,t){let n=e.selection,r=Ir(e.annotations);return e.userEvent&&(r=r.concat(xe.userEvent.of(e.userEvent))),{changes:e.changes instanceof He?e.changes:He.of(e.changes||[],t,i.facet(jm)),selection:n&&(n instanceof v?n:v.single(n.anchor,n.head)),effects:Ir(e.effects),annotations:r,scrollIntoView:!!e.scrollIntoView}}function zm(i,e,t){let n=Mf(i,e.length?e[0]:{},i.doc.length);e.length&&e[0].filter===!1&&(t=!1);for(let s=1;s<e.length;s++){e[s].filter===!1&&(t=!1);let o=!!e[s].sequential;n=Ym(n,Mf(i,e[s],o?n.changes.newLength:i.doc.length),o)}let r=xe.create(i,n.changes,n.selection,n.effects,n.annotations,n.scrollIntoView);return Tv(t?Cv(r):r)}function Cv(i){let e=i.startState,t=!0;for(let r of e.facet(Im)){let s=r(i);if(s===!1){t=!1;break}Array.isArray(s)&&(t=t===!0?s:Pv(t,s))}if(t!==!0){let r,s;if(t===!1)s=i.changes.invertedDesc,r=He.empty(e.doc.length);else{let o=i.changes.filter(t);r=o.changes,s=o.filtered.mapDesc(o.changes).invertedDesc}i=xe.create(e,r,i.selection&&i.selection.map(s),Y.mapEffects(i.effects,s),i.annotations,i.scrollIntoView)}let n=e.facet(Um);for(let r=n.length-1;r>=0;r--){let s=n[r](i);s instanceof xe?i=s:Array.isArray(s)&&s.length==1&&s[0]instanceof xe?i=s[0]:i=zm(e,Ir(s),!1)}return i}function Tv(i){let e=i.startState,t=e.facet(Bm),n=i;for(let r=t.length-1;r>=0;r--){let s=t[r](i);s&&Object.keys(s).length&&(n=Ym(n,Mf(e,s,i.changes.newLength),!0))}return n==i?i:xe.create(e,i.changes,i.selection,n.effects,n.annotations,n.scrollIntoView)}var Av=[];function Ir(i){return i==null?Av:Array.isArray(i)?i:[i]}var ae=(function(i){return i[i.Word=0]="Word",i[i.Space=1]="Space",i[i.Other=2]="Other",i})(ae||(ae={})),Mv=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,Rf;try{Rf=new RegExp("[\\p{Alphabetic}\\p{Number}_]","u")}catch{}function Rv(i){if(Rf)return Rf.test(i);for(let e=0;e<i.length;e++){let t=i[e];if(/\w/.test(t)||t>"\x80"&&(t.toUpperCase()!=t.toLowerCase()||Mv.test(t)))return!0}return!1}function Xv(i){return e=>{if(!/\S/.test(e))return ae.Space;if(Rv(e))return ae.Word;for(let t=0;t<i.length;t++)if(e.indexOf(i[t])>-1)return ae.Word;return ae.Other}}var ne=class i{constructor(e,t,n,r,s,o){this.config=e,this.doc=t,this.selection=n,this.values=r,this.status=e.statusTemplate.slice(),this.computeSlot=s,o&&(o._state=this);for(let l=0;l<this.config.dynamicSlots.length;l++)Ks(this,l<<1);this.computeSlot=null}field(e,t=!0){let n=this.config.address[e.id];if(n==null){if(t)throw new RangeError("Field is not present in this state");return}return Ks(this,n),pa(this,n)}update(...e){return zm(this,e,!0)}applyTransaction(e){let t=this.config,{base:n,compartments:r}=t;for(let l of e.effects)l.is(ua.reconfigure)?(t&&(r=new Map,t.compartments.forEach((a,c)=>r.set(c,a)),t=null),r.set(l.value.compartment,l.value.extension)):l.is(Y.reconfigure)?(t=null,n=l.value):l.is(Y.appendConfig)&&(t=null,n=Ir(n).concat(l.value));let s;t?s=e.startState.values.slice():(t=da.resolve(n,r,this),s=new i(t,this.doc,this.selection,t.dynamicSlots.map(()=>null),(a,c)=>c.reconfigure(a,this),null).values);let o=e.startState.facet(Cf)?e.newSelection:e.newSelection.asSingle();new i(t,e.newDoc,o,s,(l,a)=>a.update(l,e),e)}replaceSelection(e){return typeof e=="string"&&(e=this.toText(e)),this.changeByRange(t=>({changes:{from:t.from,to:t.to,insert:e},range:v.cursor(t.from+e.length,-1)}))}changeByRange(e){let t=this.selection,n=e(t.ranges[0]),r=this.changes(n.changes),s=[n.range],o=Ir(n.effects);for(let l=1;l<t.ranges.length;l++){let a=e(t.ranges[l]),c=this.changes(a.changes),h=c.map(r);for(let u=0;u<l;u++)s[u]=s[u].map(h);let f=r.mapDesc(c,!0);s.push(a.range.map(f)),r=r.compose(h),o=Y.mapEffects(o,h).concat(Y.mapEffects(Ir(a.effects),f))}return{changes:r,selection:v.create(s,t.mainIndex),effects:o}}changes(e=[]){return e instanceof He?e:He.of(e,this.doc.length,this.facet(i.lineSeparator))}toText(e){return q.of(e.split(this.facet(i.lineSeparator)||Qf))}sliceDoc(e=0,t=this.doc.length){return this.doc.sliceString(e,t,this.lineBreak)}facet(e){let t=this.config.address[e.id];return t==null?e.default:(Ks(this,t),pa(this,t))}toJSON(e){let t={doc:this.sliceDoc(),selection:this.selection.toJSON()};if(e)for(let n in e){let r=e[n];r instanceof de&&this.config.address[r.id]!=null&&(t[n]=r.spec.toJSON(this.field(e[n]),this))}return t}static fromJSON(e,t={},n){if(!e||typeof e.doc!="string")throw new RangeError("Invalid JSON representation for EditorState");let r=[];if(n){for(let s in n)if(Object.prototype.hasOwnProperty.call(e,s)){let o=n[s],l=e[s];r.push(o.init(a=>o.spec.fromJSON(l,a)))}}return i.create({doc:e.doc,selection:v.fromJSON(e.selection),extensions:t.extensions?r.concat([t.extensions]):r})}static create(e={}){let t=da.resolve(e.extensions||[],new Map),n=e.doc instanceof q?e.doc:q.of((e.doc||"").split(t.staticFacet(i.lineSeparator)||Qf)),r=e.selection?e.selection instanceof v?e.selection:v.single(e.selection.anchor,e.selection.head):v.single(0);return Lm(r,n.length),t.staticFacet(Cf)||(r=r.asSingle()),new i(t,n,r,t.dynamicSlots.map(()=>null),(s,o)=>o.create(s),null)}get tabSize(){return this.facet(i.tabSize)}get lineBreak(){return this.facet(i.lineSeparator)||` +`}get readOnly(){return this.facet(Vm)}phrase(e,...t){for(let n of this.facet(i.phrases))if(Object.prototype.hasOwnProperty.call(n,e)){e=n[e];break}return t.length&&(e=e.replace(/\$(\$|\d*)/g,(n,r)=>{if(r=="$")return"$";let s=+(r||1);return!s||s>t.length?n:t[s-1]})),e}languageDataAt(e,t,n=-1){let r=[];for(let s of this.facet(Dm))for(let o of s(this,t,n))Object.prototype.hasOwnProperty.call(o,e)&&r.push(o[e]);return r}charCategorizer(e){let t=this.languageDataAt("wordChars",e);return Xv(t.length?t[0]:"")}wordAt(e){let{text:t,from:n,length:r}=this.doc.lineAt(e),s=this.charCategorizer(e),o=e-n,l=e-n;for(;o>0;){let a=be(t,o,!1);if(s(t.slice(a,o))!=ae.Word)break;o=a}for(;l<r;){let a=be(t,l);if(s(t.slice(l,a))!=ae.Word)break;l=a}return o==l?null:v.range(o+n,l+n)}};ne.allowMultipleSelections=Cf;ne.tabSize=R.define({combine:i=>i.length?i[0]:4});ne.lineSeparator=jm;ne.readOnly=Vm;ne.phrases=R.define({compare(i,e){let t=Object.keys(i),n=Object.keys(e);return t.length==n.length&&t.every(r=>i[r]==e[r])}});ne.languageData=Dm;ne.changeFilter=Im;ne.transactionFilter=Um;ne.transactionExtender=Bm;ua.reconfigure=Y.define();function Ye(i,e,t={}){let n={};for(let r of i)for(let s of Object.keys(r)){let o=r[s],l=n[s];if(l===void 0)n[s]=o;else if(!(l===o||o===void 0))if(Object.hasOwnProperty.call(t,s))n[s]=t[s](l,o);else throw new Error("Config merge conflict for field "+s)}for(let r in e)n[r]===void 0&&(n[r]=e[r]);return n}var Ct=class{eq(e){return this==e}range(e,t=e){return eo.create(e,t,this)}};Ct.prototype.startSide=Ct.prototype.endSide=0;Ct.prototype.point=!1;Ct.prototype.mapMode=$e.TrackDel;function Df(i,e){return i==e||i.constructor==e.constructor&&i.eq(e)}var eo=class i{constructor(e,t,n){this.from=e,this.to=t,this.value=n}static create(e,t,n){return new i(e,t,n)}};function Xf(i,e){return i.from-e.from||i.value.startSide-e.value.startSide}var Ef=class i{constructor(e,t,n,r){this.from=e,this.to=t,this.value=n,this.maxPoint=r}get length(){return Lr(this.to)}findIndex(e,t,n,r=0){let s=n?this.to:this.from;for(let o=r,l=s.length;;){if(o==l)return o;let a=o+l>>1,c=s[a]-e||(n?this.value[a].endSide:this.value[a].startSide)-t;if(a==o)return c>=0?o:l;c>=0?l=a:o=a+1}}between(e,t,n,r){for(let s=this.findIndex(t,-1e9,!0),o=this.findIndex(n,1e9,!1,s);s<o;s++)if(r(this.from[s]+e,this.to[s]+e,this.value[s])===!1)return!1}map(e,t,n,r,s){let o=[],l=[],a=[],c=-1,h=-1;e:for(let f=0;f<this.value.length;f++){let u=this.value[f],d=this.from[f]+e,p=this.to[f]+e,O,g;if(d==p){let b=t.mapPos(d,u.startSide,u.mapMode);if(b==null||(O=g=b,u.startSide!=u.endSide&&(g=t.mapPos(d,u.endSide),g<O)))continue}else if(O=t.mapPos(d,u.startSide),g=t.mapPos(p,u.endSide),O>g||O==g&&u.startSide>0&&u.endSide<=0)continue;if(!((g-O||u.endSide-u.startSide)<0))if(c<0&&(c=O),u.point&&(h=Math.max(h,g-O)),(O-n||u.startSide-r)>=0)o.push(u),l.push(O-c),a.push(g-c),n=g,r=u.endSide;else{if(O==g)for(let b=o.length;b>0;b--){if((O-(a[b-1]+c)||u.startSide-o[b-1].endSide)>=0){o.splice(b,0,u),l.splice(b,0,O-c),a.splice(b,0,g-c);continue e}if((O-(l[b-1]+c)||u.endSide-o[b-1].startSide)>0)break}s(O,g,u)}}return{mapped:o.length?new i(l,a,o,h):null,pos:c}}},G=class i{constructor(e,t,n,r){this.chunkPos=e,this.chunk=t,this.nextLayer=n,this.maxPoint=r}static create(e,t,n,r){return new i(e,t,n,r)}get length(){let e=this.chunk.length-1;return e<0?0:Math.max(this.chunkEnd(e),this.nextLayer.length)}get size(){if(this.isEmpty)return 0;let e=this.nextLayer.size;for(let t of this.chunk)e+=t.value.length;return e}chunkEnd(e){return this.chunkPos[e]+this.chunk[e].length}update(e){let{add:t=[],sort:n=!1,filterFrom:r=0,filterTo:s=this.length}=e,o=e.filter;if(t.length==0&&!o)return this;if(n&&(t=t.slice().sort(Xf)),this.isEmpty)return t.length?i.of(t):this;let l=new Oa(this,null,-1).goto(0),a=0,c=[],h=new lt;for(;l.value||a<t.length;)if(a<t.length&&(l.from-t[a].from||l.startSide-t[a].value.startSide)>=0){let f=t[a++];h.addInner(f.from,f.to,f.value,!1)||c.push(f)}else l.rangeIndex==1&&l.chunkIndex<this.chunk.length&&(a==t.length||this.chunkEnd(l.chunkIndex)<t[a].from)&&(!o||r>this.chunkEnd(l.chunkIndex)||s<this.chunkPos[l.chunkIndex])&&h.addChunk(this.chunkPos[l.chunkIndex],this.chunk[l.chunkIndex])?l.nextChunk():((!o||r>l.to||s<l.from||o(l.from,l.to,l.value))&&(h.addInner(l.from,l.to,l.value,!1)||c.push(eo.create(l.from,l.to,l.value))),l.next());return h.finishInner(this.nextLayer.isEmpty&&!c.length?i.empty:this.nextLayer.update({add:c,filter:o,filterFrom:r,filterTo:s}))}map(e){if(e.empty||this.isEmpty)return this;let t=[],n=[],r=-1,s,o=(a,c,h)=>{s||(s=new lt),s.addRange(a,c,h,!1)};for(let a=0;a<this.chunk.length;a++){let c=this.chunkPos[a],h=this.chunk[a],f=e.touchesRange(c,c+h.length);if(f===!1)r=Math.max(r,h.maxPoint),t.push(h),n.push(e.mapPos(c));else if(f===!0){let[u,d]=t.length?[Lr(n)+Lr(t).length,Lr(Lr(t).value).endSide]:[-1,-1],{mapped:p,pos:O}=h.map(c,e,u,d,o);p&&(r=Math.max(r,p.maxPoint),t.push(p),n.push(O))}}let l=this.nextLayer.map(e);return s&&(l=s.finishInner(l)),t.length==0?l:new i(n,t,l||i.empty,r)}between(e,t,n){if(!this.isEmpty){for(let r=0;r<this.chunk.length;r++){let s=this.chunkPos[r],o=this.chunk[r];if(t>=s&&e<=s+o.length&&o.between(s,e-s,t-s,n)===!1)return}this.nextLayer.between(e,t,n)}}iter(e=0){return to.from([this]).goto(e)}get isEmpty(){return this.nextLayer==this}static iter(e,t=0){return to.from(e).goto(t)}static compare(e,t,n,r,s=-1){let o=e.filter(f=>f.maxPoint>0||!f.isEmpty&&f.maxPoint>=s),l=t.filter(f=>f.maxPoint>0||!f.isEmpty&&f.maxPoint>=s),a=Xm(o,l,n),c=new En(o,a,s),h=new En(l,a,s);n.iterGaps((f,u,d)=>Em(c,f,h,u,d,r)),n.empty&&n.length==0&&Em(c,0,h,0,0,r)}static eq(e,t,n=0,r){r==null&&(r=999999999);let s=e.filter(h=>!h.isEmpty&&t.indexOf(h)<0),o=t.filter(h=>!h.isEmpty&&e.indexOf(h)<0);if(s.length!=o.length)return!1;if(!s.length)return!0;let l=Xm(s,o),a=new En(s,l,0).goto(n),c=new En(o,l,0).goto(n);for(;;){if(a.to!=c.to||!Zf(a.active,c.active)||a.point&&(!c.point||!Df(a.point,c.point)))return!1;if(a.to>r)return!0;a.next(),c.next()}}static spans(e,t,n,r,s=-1){let o=new En(e,null,s).goto(t),l=t,a=o.openStart;for(;;){let c=Math.min(o.to,n);if(o.point){let h=o.activeForPoint(o.to),f=o.pointFrom<t?h.length+1:o.point.startSide<0?h.length:Math.min(h.length,a);r.point(l,c,o.point,h,f,o.pointRank),a=Math.min(o.openEnd(c),h.length)}else c>l&&(r.span(l,c,o.active,a),a=o.openEnd(c));if(o.to>n)return a+(o.point&&o.to>n?1:0);l=o.to,o.next()}}static of(e,t=!1){let n=new lt;for(let r of e instanceof eo?[e]:t?Ev(e):e)n.add(r.from,r.to,r.value);return n.finish()}static join(e){if(!e.length)return i.empty;let t=Lr(e);for(let n=e.length-2;n>=0;n--)for(let r=e[n];r!=i.empty;r=r.nextLayer)t=new i(r.chunkPos,r.chunk,t,Math.max(r.maxPoint,t.maxPoint));return t}};G.empty=new G([],[],null,-1);function Lr(i){return i[i.length-1]}function Ev(i){if(i.length>1)for(let e=i[0],t=1;t<i.length;t++){let n=i[t];if(Xf(e,n)>0)return i.slice().sort(Xf);e=n}return i}G.empty.nextLayer=G.empty;var lt=class i{finishChunk(e){this.chunks.push(new Ef(this.from,this.to,this.value,this.maxPoint)),this.chunkPos.push(this.chunkStart),this.chunkStart=-1,this.setMaxPoint=Math.max(this.setMaxPoint,this.maxPoint),this.maxPoint=-1,e&&(this.from=[],this.to=[],this.value=[])}constructor(){this.chunks=[],this.chunkPos=[],this.chunkStart=-1,this.last=null,this.lastFrom=-1e9,this.lastTo=-1e9,this.from=[],this.to=[],this.value=[],this.maxPoint=-1,this.setMaxPoint=-1,this.nextLayer=null}add(e,t,n){this.addRange(e,t,n,!0)}addRange(e,t,n,r){this.addInner(e,t,n,r)||(this.nextLayer||(this.nextLayer=new i)).addRange(e,t,n,r)}addInner(e,t,n,r){let s=e-this.lastTo||n.startSide-this.last.endSide;if(r&&s<=0&&(e-this.lastFrom||n.startSide-this.last.startSide)<0)throw new Error("Ranges must be added sorted by `from` position and `startSide`");return s<0?!1:(this.from.length==250&&this.finishChunk(!0),this.chunkStart<0&&(this.chunkStart=e),this.from.push(e-this.chunkStart),this.to.push(t-this.chunkStart),this.last=n,this.lastFrom=e,this.lastTo=t,this.value.push(n),n.point&&(this.maxPoint=Math.max(this.maxPoint,t-e)),!0)}addChunk(e,t){if((e-this.lastTo||t.value[0].startSide-this.last.endSide)<0)return!1;this.from.length&&this.finishChunk(!0),this.setMaxPoint=Math.max(this.setMaxPoint,t.maxPoint),this.chunks.push(t),this.chunkPos.push(e);let n=t.value.length-1;return this.last=t.value[n],this.lastFrom=t.from[n]+e,this.lastTo=t.to[n]+e,!0}finish(){return this.finishInner(G.empty)}finishInner(e){if(this.from.length&&this.finishChunk(!1),this.chunks.length==0)return e;let t=G.create(this.chunkPos,this.chunks,this.nextLayer?this.nextLayer.finishInner(e):e,this.setMaxPoint);return this.from=null,t}};function Xm(i,e,t){let n=new Map;for(let s of i)for(let o=0;o<s.chunk.length;o++)s.chunk[o].maxPoint<=0&&n.set(s.chunk[o],s.chunkPos[o]);let r=new Set;for(let s of e)for(let o=0;o<s.chunk.length;o++){let l=n.get(s.chunk[o]);l!=null&&(t?t.mapPos(l):l)==s.chunkPos[o]&&!t?.touchesRange(l,l+s.chunk[o].length)&&r.add(s.chunk[o])}return r}var Oa=class{constructor(e,t,n,r=0){this.layer=e,this.skip=t,this.minPoint=n,this.rank=r}get startSide(){return this.value?this.value.startSide:0}get endSide(){return this.value?this.value.endSide:0}goto(e,t=-1e9){return this.chunkIndex=this.rangeIndex=0,this.gotoInner(e,t,!1),this}gotoInner(e,t,n){for(;this.chunkIndex<this.layer.chunk.length;){let r=this.layer.chunk[this.chunkIndex];if(!(this.skip&&this.skip.has(r)||this.layer.chunkEnd(this.chunkIndex)<e||r.maxPoint<this.minPoint))break;this.chunkIndex++,n=!1}if(this.chunkIndex<this.layer.chunk.length){let r=this.layer.chunk[this.chunkIndex].findIndex(e-this.layer.chunkPos[this.chunkIndex],t,!0);(!n||this.rangeIndex<r)&&this.setRangeIndex(r)}this.next()}forward(e,t){(this.to-e||this.endSide-t)<0&&this.gotoInner(e,t,!0)}next(){for(;;)if(this.chunkIndex==this.layer.chunk.length){this.from=this.to=1e9,this.value=null;break}else{let e=this.layer.chunkPos[this.chunkIndex],t=this.layer.chunk[this.chunkIndex],n=e+t.from[this.rangeIndex];if(this.from=n,this.to=e+t.to[this.rangeIndex],this.value=t.value[this.rangeIndex],this.setRangeIndex(this.rangeIndex+1),this.minPoint<0||this.value.point&&this.to-this.from>=this.minPoint)break}}setRangeIndex(e){if(e==this.layer.chunk[this.chunkIndex].value.length){if(this.chunkIndex++,this.skip)for(;this.chunkIndex<this.layer.chunk.length&&this.skip.has(this.layer.chunk[this.chunkIndex]);)this.chunkIndex++;this.rangeIndex=0}else this.rangeIndex=e}nextChunk(){this.chunkIndex++,this.rangeIndex=0,this.next()}compare(e){return this.from-e.from||this.startSide-e.startSide||this.rank-e.rank||this.to-e.to||this.endSide-e.endSide}},to=class i{constructor(e){this.heap=e}static from(e,t=null,n=-1){let r=[];for(let s=0;s<e.length;s++)for(let o=e[s];!o.isEmpty;o=o.nextLayer)o.maxPoint>=n&&r.push(new Oa(o,t,n,s));return r.length==1?r[0]:new i(r)}get startSide(){return this.value?this.value.startSide:0}goto(e,t=-1e9){for(let n of this.heap)n.goto(e,t);for(let n=this.heap.length>>1;n>=0;n--)wf(this.heap,n);return this.next(),this}forward(e,t){for(let n of this.heap)n.forward(e,t);for(let n=this.heap.length>>1;n>=0;n--)wf(this.heap,n);(this.to-e||this.value.endSide-t)<0&&this.next()}next(){if(this.heap.length==0)this.from=this.to=1e9,this.value=null,this.rank=-1;else{let e=this.heap[0];this.from=e.from,this.to=e.to,this.value=e.value,this.rank=e.rank,e.value&&e.next(),wf(this.heap,0)}}};function wf(i,e){for(let t=i[e];;){let n=(e<<1)+1;if(n>=i.length)break;let r=i[n];if(n+1<i.length&&r.compare(i[n+1])>=0&&(r=i[n+1],n++),t.compare(r)<0)break;i[n]=t,i[e]=r,e=n}}var En=class{constructor(e,t,n){this.minPoint=n,this.active=[],this.activeTo=[],this.activeRank=[],this.minActive=-1,this.point=null,this.pointFrom=0,this.pointRank=0,this.to=-1e9,this.endSide=0,this.openStart=-1,this.cursor=to.from(e,t,n)}goto(e,t=-1e9){return this.cursor.goto(e,t),this.active.length=this.activeTo.length=this.activeRank.length=0,this.minActive=-1,this.to=e,this.endSide=t,this.openStart=-1,this.next(),this}forward(e,t){for(;this.minActive>-1&&(this.activeTo[this.minActive]-e||this.active[this.minActive].endSide-t)<0;)this.removeActive(this.minActive);this.cursor.forward(e,t)}removeActive(e){oa(this.active,e),oa(this.activeTo,e),oa(this.activeRank,e),this.minActive=Zm(this.active,this.activeTo)}addActive(e){let t=0,{value:n,to:r,rank:s}=this.cursor;for(;t<this.activeRank.length&&(s-this.activeRank[t]||r-this.activeTo[t])>0;)t++;la(this.active,t,n),la(this.activeTo,t,r),la(this.activeRank,t,s),e&&la(e,t,this.cursor.from),this.minActive=Zm(this.active,this.activeTo)}next(){let e=this.to,t=this.point;this.point=null;let n=this.openStart<0?[]:null;for(;;){let r=this.minActive;if(r>-1&&(this.activeTo[r]-this.cursor.from||this.active[r].endSide-this.cursor.startSide)<0){if(this.activeTo[r]>e){this.to=this.activeTo[r],this.endSide=this.active[r].endSide;break}this.removeActive(r),n&&oa(n,r)}else if(this.cursor.value)if(this.cursor.from>e){this.to=this.cursor.from,this.endSide=this.cursor.startSide;break}else{let s=this.cursor.value;if(!s.point)this.addActive(n),this.cursor.next();else if(t&&this.cursor.to==this.to&&this.cursor.from<this.cursor.to)this.cursor.next();else{this.point=s,this.pointFrom=this.cursor.from,this.pointRank=this.cursor.rank,this.to=this.cursor.to,this.endSide=s.endSide,this.cursor.next(),this.forward(this.to,this.endSide);break}}else{this.to=this.endSide=1e9;break}}if(n){this.openStart=0;for(let r=n.length-1;r>=0&&n[r]<e;r--)this.openStart++}}activeForPoint(e){if(!this.active.length)return this.active;let t=[];for(let n=this.active.length-1;n>=0&&!(this.activeRank[n]<this.pointRank);n--)(this.activeTo[n]>e||this.activeTo[n]==e&&this.active[n].endSide>=this.point.endSide)&&t.push(this.active[n]);return t.reverse()}openEnd(e){let t=0;for(let n=this.activeTo.length-1;n>=0&&this.activeTo[n]>e;n--)t++;return t}};function Em(i,e,t,n,r,s){i.goto(e),t.goto(n);let o=n+r,l=n,a=n-e,c=!!s.boundChange;for(let h=!1;;){let f=i.to+a-t.to,u=f||i.endSide-t.endSide,d=u<0?i.to+a:t.to,p=Math.min(d,o);if(i.point||t.point?(i.point&&t.point&&Df(i.point,t.point)&&Zf(i.activeForPoint(i.to),t.activeForPoint(t.to))||s.comparePoint(l,p,i.point,t.point),h=!1):(h&&(s.boundChange(l),h=!1),p>l&&!Zf(i.active,t.active)&&s.compareRange(l,p,i.active,t.active),c&&p<o&&(f||i.openEnd(d)!=t.openEnd(d))&&(h=!0)),d>o)break;l=d,u<=0&&i.next(),u>=0&&t.next()}}function Zf(i,e){if(i.length!=e.length)return!1;for(let t=0;t<i.length;t++)if(i[t]!=e[t]&&!Df(i[t],e[t]))return!1;return!0}function oa(i,e){for(let t=e,n=i.length-1;t<n;t++)i[t]=i[t+1];i.pop()}function la(i,e,t){for(let n=i.length-1;n>=e;n--)i[n+1]=i[n];i[e]=t}function Zm(i,e){let t=-1,n=1e9;for(let r=0;r<e.length;r++)(e[r]-n||i[r].endSide-i[t].endSide)<0&&(t=r,n=e[r]);return t}function Ke(i,e,t=i.length){let n=0;for(let r=0;r<t&&r<i.length;)i.charCodeAt(r)==9?(n+=e-n%e,r++):(n++,r=be(i,r));return n}function ga(i,e,t,n){for(let r=0,s=0;;){if(s>=e)return r;if(r==i.length)break;s+=i.charCodeAt(r)==9?t-s%t:1,r=be(i,r)}return n===!0?-1:i.length}var Wm=typeof Symbol>"u"?"__\u037C":Symbol.for("\u037C"),If=typeof Symbol>"u"?"__styleSet"+Math.floor(Math.random()*1e8):Symbol("styleSet"),Nm=typeof globalThis<"u"?globalThis:typeof window<"u"?window:{},Tt=class{constructor(e,t){this.rules=[];let{finish:n}=t||{};function r(o){return/^@/.test(o)?[o]:o.split(/,\s*/)}function s(o,l,a,c){let h=[],f=/^@(\w+)\b/.exec(o[0]),u=f&&f[1]=="keyframes";if(f&&l==null)return a.push(o[0]+";");for(let d in l){let p=l[d];if(/&/.test(d))s(d.split(/,\s*/).map(O=>o.map(g=>O.replace(/&/,g))).reduce((O,g)=>O.concat(g)),p,a);else if(p&&typeof p=="object"){if(!f)throw new RangeError("The value of a property ("+d+") should be a primitive value.");s(r(d),p,h,u)}else p!=null&&h.push(d.replace(/_.*/,"").replace(/[A-Z]/g,O=>"-"+O.toLowerCase())+": "+p+";")}(h.length||u)&&a.push((n&&!f&&!c?o.map(n):o).join(", ")+" {"+h.join(" ")+"}")}for(let o in e)s(r(o),e[o],this.rules)}getRules(){return this.rules.join(` +`)}static newName(){let e=Nm[Wm]||1;return Nm[Wm]=e+1,"\u037C"+e.toString(36)}static mount(e,t,n){let r=e[If],s=n&&n.nonce;r?s&&r.setNonce(s):r=new Uf(e,s),r.mount(Array.isArray(t)?t:[t],e)}},qm=new Map,Uf=class{constructor(e,t){let n=e.ownerDocument||e,r=n.defaultView;if(!e.head&&e.adoptedStyleSheets&&r.CSSStyleSheet){let s=qm.get(n);if(s)return e[If]=s;this.sheet=new r.CSSStyleSheet,qm.set(n,this)}else this.styleTag=n.createElement("style"),t&&this.styleTag.setAttribute("nonce",t);this.modules=[],e[If]=this}mount(e,t){let n=this.sheet,r=0,s=0,o=!1;for(let l=0;l<e.length;l++){let a=e[l],c=this.modules.indexOf(a);if(c<s&&c>-1&&(this.modules.splice(c,1),o=!0,s--,c=-1),c==-1){if(this.modules.splice(s++,0,a),o=!0,n)for(let h=0;h<a.rules.length;h++)n.insertRule(a.rules[h],r++)}else{for(;s<c;)r+=this.modules[s++].rules.length;r+=a.rules.length,s++}}if(n)t.adoptedStyleSheets.indexOf(this.sheet)<0&&(t.adoptedStyleSheets=[this.sheet,...t.adoptedStyleSheets]);else{if(o){let a="";for(let c=0;c<this.modules.length;c++)a+=this.modules[c].getRules()+` +`;this.styleTag.textContent=a}let l=t.head||t;this.styleTag.parentNode!=l&&l.insertBefore(this.styleTag,l.firstChild)}}setNonce(e){this.styleTag&&this.styleTag.getAttribute("nonce")!=e&&this.styleTag.setAttribute("nonce",e)}};var Si={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},Br={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},Zv=typeof navigator<"u"&&/Mac/.test(navigator.platform),_v=typeof navigator<"u"&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);for(ke=0;ke<10;ke++)Si[48+ke]=Si[96+ke]=String(ke);var ke;for(ke=1;ke<=24;ke++)Si[ke+111]="F"+ke;var ke;for(ke=65;ke<=90;ke++)Si[ke]=String.fromCharCode(ke+32),Br[ke]=String.fromCharCode(ke);var ke;for(ma in Si)Br.hasOwnProperty(ma)||(Br[ma]=Si[ma]);var ma;function Gm(i){var e=Zv&&i.metaKey&&i.shiftKey&&!i.ctrlKey&&!i.altKey||_v&&i.shiftKey&&i.key&&i.key.length==1||i.key=="Unidentified",t=!e&&i.key||(i.shiftKey?Br:Si)[i.keyCode]||i.key||"Unidentified";return t=="Esc"&&(t="Escape"),t=="Del"&&(t="Delete"),t=="Left"&&(t="ArrowLeft"),t=="Up"&&(t="ArrowUp"),t=="Right"&&(t="ArrowRight"),t=="Down"&&(t="ArrowDown"),t}function te(){var i=arguments[0];typeof i=="string"&&(i=document.createElement(i));var e=1,t=arguments[1];if(t&&typeof t=="object"&&t.nodeType==null&&!Array.isArray(t)){for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){var r=t[n];typeof r=="string"?i.setAttribute(n,r):r!=null&&(i[n]=r)}e++}for(;e<arguments.length;e++)Fm(i,arguments[e]);return i}function Fm(i,e){if(typeof e=="string")i.appendChild(document.createTextNode(e));else if(e!=null)if(e.nodeType!=null)i.appendChild(e);else if(Array.isArray(e))for(var t=0;t<e.length;t++)Fm(i,e[t]);else throw new RangeError("Unsupported child node: "+e)}var Je=typeof navigator<"u"?navigator:{userAgent:"",vendor:"",platform:""},Ff=typeof document<"u"?document:{documentElement:{style:{}}},Hf=/Edge\/(\d+)/.exec(Je.userAgent),Z0=/MSIE \d/.test(Je.userAgent),Kf=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(Je.userAgent),Ha=!!(Z0||Kf||Hf),Hm=!Ha&&/gecko\/(\d+)/i.test(Je.userAgent),Bf=!Ha&&/Chrome\/(\d+)/.exec(Je.userAgent),Km="webkitFontSmoothing"in Ff.documentElement.style,Jf=!Ha&&/Apple Computer/.test(Je.vendor),Jm=Jf&&(/Mobile\/\w+/.test(Je.userAgent)||Je.maxTouchPoints>2),_={mac:Jm||/Mac/.test(Je.platform),windows:/Win/.test(Je.platform),linux:/Linux|X11/.test(Je.platform),ie:Ha,ie_version:Z0?Ff.documentMode||6:Kf?+Kf[1]:Hf?+Hf[1]:0,gecko:Hm,gecko_version:Hm?+(/Firefox\/(\d+)/.exec(Je.userAgent)||[0,0])[1]:0,chrome:!!Bf,chrome_version:Bf?+Bf[1]:0,ios:Jm,android:/Android\b/.test(Je.userAgent),webkit:Km,webkit_version:Km?+(/\bAppleWebKit\/(\d+)/.exec(Je.userAgent)||[0,0])[1]:0,safari:Jf,safari_version:Jf?+(/\bVersion\/(\d+(\.\d+)?)/.exec(Je.userAgent)||[0,0])[1]:0,tabSize:Ff.documentElement.style.tabSize!=null?"tab-size":"-moz-tab-size"};function zu(i,e){for(let t in i)t=="class"&&e.class?e.class+=" "+i.class:t=="style"&&e.style?e.style+=";"+i.style:e[t]=i[t];return e}var Xa=Object.create(null);function Wu(i,e,t){if(i==e)return!0;i||(i=Xa),e||(e=Xa);let n=Object.keys(i),r=Object.keys(e);if(n.length-(t&&n.indexOf(t)>-1?1:0)!=r.length-(t&&r.indexOf(t)>-1?1:0))return!1;for(let s of n)if(s!=t&&(r.indexOf(s)==-1||i[s]!==e[s]))return!1;return!0}function Lv(i,e){for(let t=i.attributes.length-1;t>=0;t--){let n=i.attributes[t].name;e[n]==null&&i.removeAttribute(n)}for(let t in e){let n=e[t];t=="style"?i.style.cssText=n:i.getAttribute(t)!=n&&i.setAttribute(t,n)}}function e0(i,e,t){let n=!1;if(e)for(let r in e)t&&r in t||(n=!0,r=="style"?i.style.cssText="":i.removeAttribute(r));if(t)for(let r in t)e&&e[r]==t[r]||(n=!0,r=="style"?i.style.cssText=t[r]:i.setAttribute(r,t[r]));return n}function Dv(i){let e=Object.create(null);for(let t=0;t<i.attributes.length;t++){let n=i.attributes[t];e[n.name]=n.value}return e}var De=class{eq(e){return!1}updateDOM(e,t,n){return!1}compare(e){return this==e||this.constructor==e.constructor&&this.eq(e)}get estimatedHeight(){return-1}get lineBreaks(){return 0}ignoreEvent(e){return!0}coordsAt(e,t,n){return null}get isHidden(){return!1}get editable(){return!1}destroy(e){}},Le=(function(i){return i[i.Text=0]="Text",i[i.WidgetBefore=1]="WidgetBefore",i[i.WidgetAfter=2]="WidgetAfter",i[i.WidgetRange=3]="WidgetRange",i})(Le||(Le={})),L=class extends Ct{constructor(e,t,n,r){super(),this.startSide=e,this.endSide=t,this.widget=n,this.spec=r}get heightRelevant(){return!1}static mark(e){return new go(e)}static widget(e){let t=Math.max(-1e4,Math.min(1e4,e.side||0)),n=!!e.block;return t+=n&&!e.inlineOrder?t>0?3e8:-4e8:t>0?1e8:-1e8,new In(e,t,t,n,e.widget||null,!1)}static replace(e){let t=!!e.block,n,r;if(e.isBlockGap)n=-5e8,r=4e8;else{let{start:s,end:o}=_0(e,t);n=(s?t?-3e8:-1:5e8)-1,r=(o?t?2e8:1:-6e8)+1}return new In(e,n,r,t,e.widget||null,!0)}static line(e){return new mo(e)}static set(e,t=!1){return G.of(e,t)}hasHeight(){return this.widget?this.widget.estimatedHeight>-1:!1}};L.none=G.empty;var go=class i extends L{constructor(e){let{start:t,end:n}=_0(e);super(t?-1:5e8,n?1:-6e8,null,e),this.tagName=e.tagName||"span",this.attrs=e.class&&e.attributes?zu(e.attributes,{class:e.class}):e.class?{class:e.class}:e.attributes||Xa}eq(e){return this==e||e instanceof i&&this.tagName==e.tagName&&Wu(this.attrs,e.attrs)}range(e,t=e){if(e>=t)throw new RangeError("Mark decorations may not be empty");return super.range(e,t)}};go.prototype.point=!1;var mo=class i extends L{constructor(e){super(-2e8,-2e8,null,e)}eq(e){return e instanceof i&&this.spec.class==e.spec.class&&Wu(this.spec.attributes,e.spec.attributes)}range(e,t=e){if(t!=e)throw new RangeError("Line decoration ranges must be zero-length");return super.range(e,t)}};mo.prototype.mapMode=$e.TrackBefore;mo.prototype.point=!0;var In=class i extends L{constructor(e,t,n,r,s,o){super(t,n,s,e),this.block=r,this.isReplace=o,this.mapMode=r?t<=0?$e.TrackBefore:$e.TrackAfter:$e.TrackDel}get type(){return this.startSide!=this.endSide?Le.WidgetRange:this.startSide<=0?Le.WidgetBefore:Le.WidgetAfter}get heightRelevant(){return this.block||!!this.widget&&(this.widget.estimatedHeight>=5||this.widget.lineBreaks>0)}eq(e){return e instanceof i&&jv(this.widget,e.widget)&&this.block==e.block&&this.startSide==e.startSide&&this.endSide==e.endSide}range(e,t=e){if(this.isReplace&&(e>t||e==t&&this.startSide>0&&this.endSide<=0))throw new RangeError("Invalid range for replacement decoration");if(!this.isReplace&&t!=e)throw new RangeError("Widget decorations can only have zero-length ranges");return super.range(e,t)}};In.prototype.point=!0;function _0(i,e=!1){let{inclusiveStart:t,inclusiveEnd:n}=i;return t==null&&(t=i.inclusive),n==null&&(n=i.inclusive),{start:t??e,end:n??e}}function jv(i,e){return i==e||!!(i&&e&&i.compare(e))}function qr(i,e,t,n=0){let r=t.length-1;r>=0&&t[r]+n>=i?t[r]=Math.max(t[r],e):t.push(i,e)}var Ea=class i extends Ct{constructor(e,t,n){super(),this.tagName=e,this.attributes=t,this.rank=n}eq(e){return e==this||e instanceof i&&this.tagName==e.tagName&&Wu(this.attributes,e.attributes)}static create(e){return new i(e.tagName,e.attributes||Xa,e.rank==null?50:Math.max(0,Math.min(e.rank,100)))}static set(e,t=!1){return G.of(e,t)}};Ea.prototype.startSide=Ea.prototype.endSide=-1;function bo(i){let e;return i.nodeType==11?e=i.getSelection?i:i.ownerDocument:e=i,e.getSelection()}function eu(i,e){return e?i==e||i.contains(e.nodeType!=1?e.parentNode:e):!1}function oo(i,e){if(!e.anchorNode)return!1;try{return eu(i,e.anchorNode)}catch{return!1}}function Pa(i){return i.nodeType==3?yo(i,0,i.nodeValue.length).getClientRects():i.nodeType==1?i.getClientRects():[]}function lo(i,e,t,n){return t?t0(i,e,t,n,-1)||t0(i,e,t,n,1):!1}function Bi(i){for(var e=0;;e++)if(i=i.previousSibling,!i)return e}function Za(i){return i.nodeType==1&&/^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(i.nodeName)}function t0(i,e,t,n,r){for(;;){if(i==t&&e==n)return!0;if(e==(r<0?0:ki(i))){if(i.nodeName=="DIV")return!1;let s=i.parentNode;if(!s||s.nodeType!=1)return!1;e=Bi(i)+(r<0?0:1),i=s}else if(i.nodeType==1){if(i=i.childNodes[e+(r<0?-1:0)],i.nodeType==1&&i.contentEditable=="false")return!1;e=r<0?ki(i):0}else return!1}}function ki(i){return i.nodeType==3?i.nodeValue.length:i.childNodes.length}function _a(i,e){let{left:t,right:n}=i;if(t==n)return i;let r=e?t:n;return{left:r,right:r,top:i.top,bottom:i.bottom}}function Iv(i){let e=i.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:i.innerWidth,top:0,bottom:i.innerHeight}}function L0(i,e){let t=e.width/i.offsetWidth,n=e.height/i.offsetHeight;return(t>.995&&t<1.005||!isFinite(t)||Math.abs(e.width-i.offsetWidth)<1)&&(t=1),(n>.995&&n<1.005||!isFinite(n)||Math.abs(e.height-i.offsetHeight)<1)&&(n=1),{scaleX:t,scaleY:n}}function Uv(i,e,t,n,r,s,o,l){let a=i.ownerDocument,c=a.defaultView||window;for(let h=i,f=!1;h&&!f;)if(h.nodeType==1){let u,d=h==a.body,p=1,O=1;if(d)u=Iv(c);else{if(/^(fixed|sticky)$/.test(getComputedStyle(h).position)&&(f=!0),h.scrollHeight<=h.clientHeight&&h.scrollWidth<=h.clientWidth){h=h.assignedSlot||h.parentNode;continue}let w=h.getBoundingClientRect();({scaleX:p,scaleY:O}=L0(h,w)),u={left:w.left,right:w.left+h.clientWidth*p,top:w.top,bottom:w.top+h.clientHeight*O}}let g=0,b=0;if(r=="nearest")e.top<u.top+o?(b=e.top-(u.top+o),t>0&&e.bottom>u.bottom+b&&(b=e.bottom-u.bottom+o)):e.bottom>u.bottom-o&&(b=e.bottom-u.bottom+o,t<0&&e.top-b<u.top&&(b=e.top-(u.top+o)));else{let w=e.bottom-e.top,x=u.bottom-u.top;b=(r=="center"&&w<=x?e.top+w/2-x/2:r=="start"||r=="center"&&t<0?e.top-o:e.bottom-x+o)-u.top}if(n=="nearest"?e.left<u.left+s?(g=e.left-(u.left+s),t>0&&e.right>u.right+g&&(g=e.right-u.right+s)):e.right>u.right-s&&(g=e.right-u.right+s,t<0&&e.left<u.left+g&&(g=e.left-(u.left+s))):g=(n=="center"?e.left+(e.right-e.left)/2-(u.right-u.left)/2:n=="start"==l?e.left-s:e.right-(u.right-u.left)+s)-u.left,g||b)if(d)c.scrollBy(g,b);else{let w=0,x=0;if(b){let X=h.scrollTop;h.scrollTop+=b/O,x=(h.scrollTop-X)*O}if(g){let X=h.scrollLeft;h.scrollLeft+=g/p,w=(h.scrollLeft-X)*p}e={left:e.left-w,top:e.top-x,right:e.right-w,bottom:e.bottom-x},w&&Math.abs(w-g)<1&&(n="nearest"),x&&Math.abs(x-b)<1&&(r="nearest")}if(d)break;(e.top<u.top||e.bottom>u.bottom||e.left<u.left||e.right>u.right)&&(e={left:Math.max(e.left,u.left),right:Math.min(e.right,u.right),top:Math.max(e.top,u.top),bottom:Math.min(e.bottom,u.bottom)}),h=h.assignedSlot||h.parentNode}else if(h.nodeType==11)h=h.host;else break}function D0(i,e=!0){let t=i.ownerDocument,n=null,r=null;for(let s=i.parentNode;s&&!(s==t.body||(!e||n)&&r);)if(s.nodeType==1)!r&&s.scrollHeight>s.clientHeight&&(r=s),e&&!n&&s.scrollWidth>s.clientWidth&&(n=s),s=s.assignedSlot||s.parentNode;else if(s.nodeType==11)s=s.host;else break;return{x:n,y:r}}var tu=class{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}eq(e){return this.anchorNode==e.anchorNode&&this.anchorOffset==e.anchorOffset&&this.focusNode==e.focusNode&&this.focusOffset==e.focusOffset}setRange(e){let{anchorNode:t,focusNode:n}=e;this.set(t,Math.min(e.anchorOffset,t?ki(t):0),n,Math.min(e.focusOffset,n?ki(n):0))}set(e,t,n,r){this.anchorNode=e,this.anchorOffset=t,this.focusNode=n,this.focusOffset=r}};function j0(i){let e=[];for(let t=i;t;t=t.nodeType==11?t.host:t.parentNode)t.nodeType==1&&e.push({node:t,left:t.scrollLeft,top:t.scrollTop});return e}function I0(i,e=!0){for(let{node:t,left:n,top:r}of i)e&&t.scrollTop!=r&&(t.scrollTop=r),t.scrollLeft!=n&&(t.scrollLeft=n)}var Ln=null;_.safari&&_.safari_version>=26&&(Ln=!1);function U0(i){if(i.setActive)return i.setActive();if(Ln)return i.focus(Ln);let e=j0(i);i.focus(Ln==null?{get preventScroll(){return Ln={preventScroll:!0},!0}}:void 0),Ln||(Ln=!1,I0(e))}var i0;function yo(i,e,t=e){let n=i0||(i0=document.createRange());return n.setEnd(i,t),n.setStart(i,e),n}function Gr(i,e,t,n){let r={key:e,code:e,keyCode:t,which:t,cancelable:!0};n&&({altKey:r.altKey,ctrlKey:r.ctrlKey,shiftKey:r.shiftKey,metaKey:r.metaKey}=n);let s=new KeyboardEvent("keydown",r);s.synthetic=!0,i.dispatchEvent(s);let o=new KeyboardEvent("keyup",r);return o.synthetic=!0,i.dispatchEvent(o),s.defaultPrevented||o.defaultPrevented}function Bv(i){for(;i;){if(i&&(i.nodeType==9||i.nodeType==11&&i.host))return i;i=i.assignedSlot||i.parentNode}return null}function Vv(i,e){let t=e.focusNode,n=e.focusOffset;if(!t||e.anchorNode!=t||e.anchorOffset!=n)return!1;for(n=Math.min(n,ki(t));;)if(n){if(t.nodeType!=1)return!1;let r=t.childNodes[n-1];r.contentEditable=="false"?n--:(t=r,n=ki(t))}else{if(t==i)return!0;n=Bi(t),t=t.parentNode}}function B0(i){return i instanceof Window?i.pageYOffset>Math.max(0,i.document.documentElement.scrollHeight-i.innerHeight-4):i.scrollTop>Math.max(1,i.scrollHeight-i.clientHeight-4)}function V0(i,e){for(let t=i,n=e;;){if(t.nodeType==3&&n>0)return{node:t,offset:n};if(t.nodeType==1&&n>0){if(t.contentEditable=="false")return null;t=t.childNodes[n-1],n=ki(t)}else if(t.parentNode&&!Za(t))n=Bi(t),t=t.parentNode;else return null}}function Y0(i,e){for(let t=i,n=e;;){if(t.nodeType==3&&n<t.nodeValue.length)return{node:t,offset:n};if(t.nodeType==1&&n<t.childNodes.length){if(t.contentEditable=="false")return null;t=t.childNodes[n],n=0}else if(t.parentNode&&!Za(t))n=Bi(t)+1,t=t.parentNode;else return null}}var li=class i{constructor(e,t,n=!0){this.node=e,this.offset=t,this.precise=n}static before(e,t){return new i(e.parentNode,Bi(e),t)}static after(e,t){return new i(e.parentNode,Bi(e)+1,t)}},re=(function(i){return i[i.LTR=0]="LTR",i[i.RTL=1]="RTL",i})(re||(re={})),Un=re.LTR,Nu=re.RTL;function z0(i){let e=[];for(let t=0;t<i.length;t++)e.push(1<<+i[t]);return e}var Yv=z0("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008"),zv=z0("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333"),iu=Object.create(null),oi=[];for(let i of["()","[]","{}"]){let e=i.charCodeAt(0),t=i.charCodeAt(1);iu[e]=t,iu[t]=-e}function W0(i){return i<=247?Yv[i]:1424<=i&&i<=1524?2:1536<=i&&i<=1785?zv[i-1536]:1774<=i&&i<=2220?4:8192<=i&&i<=8204?256:64336<=i&&i<=65023?4:1}var Wv=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/,Rt=class{get dir(){return this.level%2?Nu:Un}constructor(e,t,n){this.from=e,this.to=t,this.level=n}side(e,t){return this.dir==t==e?this.to:this.from}forward(e,t){return e==(this.dir==t)}static find(e,t,n,r){let s=-1;for(let o=0;o<e.length;o++){let l=e[o];if(l.from<=t&&l.to>=t){if(l.level==n)return o;(s<0||(r!=0?r<0?l.from<t:l.to>t:e[s].level>l.level))&&(s=o)}}if(s<0)throw new RangeError("Index out of range");return s}};function N0(i,e){if(i.length!=e.length)return!1;for(let t=0;t<i.length;t++){let n=i[t],r=e[t];if(n.from!=r.from||n.to!=r.to||n.direction!=r.direction||!N0(n.inner,r.inner))return!1}return!0}var ce=[];function Nv(i,e,t,n,r){for(let s=0;s<=n.length;s++){let o=s?n[s-1].to:e,l=s<n.length?n[s].from:t,a=s?256:r;for(let c=o,h=a,f=a;c<l;c++){let u=W0(i.charCodeAt(c));u==512?u=h:u==8&&f==4&&(u=16),ce[c]=u==4?2:u,u&7&&(f=u),h=u}for(let c=o,h=a,f=a;c<l;c++){let u=ce[c];if(u==128)c<l-1&&h==ce[c+1]&&h&24?u=ce[c]=h:ce[c]=256;else if(u==64){let d=c+1;for(;d<l&&ce[d]==64;)d++;let p=c&&h==8||d<t&&ce[d]==8?f==1?1:8:256;for(let O=c;O<d;O++)ce[O]=p;c=d-1}else u==8&&f==1&&(ce[c]=1);h=u,u&7&&(f=u)}}}function qv(i,e,t,n,r){let s=r==1?2:1;for(let o=0,l=0,a=0;o<=n.length;o++){let c=o?n[o-1].to:e,h=o<n.length?n[o].from:t;for(let f=c,u,d,p;f<h;f++)if(d=iu[u=i.charCodeAt(f)])if(d<0){for(let O=l-3;O>=0;O-=3)if(oi[O+1]==-d){let g=oi[O+2],b=g&2?r:g&4?g&1?s:r:0;b&&(ce[f]=ce[oi[O]]=b),l=O;break}}else{if(oi.length==189)break;oi[l++]=f,oi[l++]=u,oi[l++]=a}else if((p=ce[f])==2||p==1){let O=p==r;a=O?0:1;for(let g=l-3;g>=0;g-=3){let b=oi[g+2];if(b&2)break;if(O)oi[g+2]|=2;else{if(b&4)break;oi[g+2]|=4}}}}}function Gv(i,e,t,n){for(let r=0,s=n;r<=t.length;r++){let o=r?t[r-1].to:i,l=r<t.length?t[r].from:e;for(let a=o;a<l;){let c=ce[a];if(c==256){let h=a+1;for(;;)if(h==l){if(r==t.length)break;h=t[r++].to,l=r<t.length?t[r].from:e}else if(ce[h]==256)h++;else break;let f=s==1,u=(h<e?ce[h]:n)==1,d=f==u?f?1:2:n;for(let p=h,O=r,g=O?t[O-1].to:i;p>a;)p==g&&(p=t[--O].from,g=O?t[O-1].to:i),ce[--p]=d;a=h}else s=c,a++}}}function nu(i,e,t,n,r,s,o){let l=n%2?2:1;if(n%2==r%2)for(let a=e,c=0;a<t;){let h=!0,f=!1;if(c==s.length||a<s[c].from){let O=ce[a];O!=l&&(h=!1,f=O==16)}let u=!h&&l==1?[]:null,d=h?n:n+1,p=a;e:for(;;)if(c<s.length&&p==s[c].from){if(f)break e;let O=s[c];if(!h)for(let g=O.to,b=c+1;;){if(g==t)break e;if(b<s.length&&s[b].from==g)g=s[b++].to;else{if(ce[g]==l)break e;break}}if(c++,u)u.push(O);else{O.from>a&&o.push(new Rt(a,O.from,d));let g=O.direction==Un!=!(d%2);ru(i,g?n+1:n,r,O.inner,O.from,O.to,o),a=O.to}p=O.to}else{if(p==t||(h?ce[p]!=l:ce[p]==l))break;p++}u?nu(i,a,p,n+1,r,u,o):a<p&&o.push(new Rt(a,p,d)),a=p}else for(let a=t,c=s.length;a>e;){let h=!0,f=!1;if(!c||a>s[c-1].to){let O=ce[a-1];O!=l&&(h=!1,f=O==16)}let u=!h&&l==1?[]:null,d=h?n:n+1,p=a;e:for(;;)if(c&&p==s[c-1].to){if(f)break e;let O=s[--c];if(!h)for(let g=O.from,b=c;;){if(g==e)break e;if(b&&s[b-1].to==g)g=s[--b].from;else{if(ce[g-1]==l)break e;break}}if(u)u.push(O);else{O.to<a&&o.push(new Rt(O.to,a,d));let g=O.direction==Un!=!(d%2);ru(i,g?n+1:n,r,O.inner,O.from,O.to,o),a=O.from}p=O.from}else{if(p==e||(h?ce[p-1]!=l:ce[p-1]==l))break;p--}u?nu(i,p,a,n+1,r,u,o):p<a&&o.push(new Rt(p,a,d)),a=p}}function ru(i,e,t,n,r,s,o){let l=e%2?2:1;Nv(i,r,s,n,l),qv(i,r,s,n,l),Gv(r,s,n,l),nu(i,r,s,e,t,n,o)}function Fv(i,e,t){if(!i)return[new Rt(0,0,e==Nu?1:0)];if(e==Un&&!t.length&&!Wv.test(i))return q0(i.length);if(t.length)for(;i.length>ce.length;)ce[ce.length]=256;let n=[],r=e==Un?0:1;return ru(i,r,r,t,0,i.length,n),n}function q0(i){return[new Rt(0,i,0)]}var G0="";function Hv(i,e,t,n,r){var s;if(!i.length)return null;let o=n.head-i.from,l;if(n.head==i.from&&n.assoc<0){if(!r)return null;o=e[l=0].side(!1,t)}else if(n.head==i.to&&n.assoc>0){if(r)return null;o=e[l=e.length-1].side(!0,t)}else l=Rt.find(e,o,(s=n.bidiLevel)!==null&&s!==void 0?s:-1,n.assoc);let a=e[l],c=a.side(r,t);if(o==c){let u=l+=r?1:-1;if(u<0||u>=e.length)return null;a=e[l=u],o=a.side(!r,t),c=a.side(r,t)}let h=be(i.text,o,a.forward(r,t));(h<a.from||h>a.to)&&(h=c),G0=i.text.slice(Math.min(o,h),Math.max(o,h));let f=l==(r?e.length-1:0)?null:e[l+(r?1:-1)];if(h==c){if(!f)return r?v.cursor(i.to,1):v.cursor(i.from,-1);if(f.level+(r?0:1)<a.level)return v.cursor(f.side(!r,t)+i.from,f.forward(r,t)?1:-1,f.level)}return v.cursor(h+i.from,a.forward(r,t)?-1:1,a.level)}function Kv(i,e,t){for(let n=e;n<t;n++){let r=W0(i.charCodeAt(n));if(r==1)return Un;if(r==2||r==4)return Nu}return Un}var F0=R.define(),H0=R.define(),K0=R.define(),J0=R.define(),su=R.define(),e1=R.define(),t1=R.define(),qu=R.define(),Gu=R.define(),i1=R.define({combine:i=>i.some(e=>e)}),n1=R.define({combine:i=>i.some(e=>e)}),r1=R.define(),ao=class i{constructor(e,t,n,r,s,o=!1){this.range=e,this.y=t,this.x=n,this.yMargin=r,this.xMargin=s,this.isSnapshot=o}map(e){return e.empty?this:new i(this.range.map(e),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}clip(e){return this.range.to<=e.doc.length?this:new i(v.cursor(e.doc.length),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}},ba=Y.define({map:(i,e)=>i.map(e)}),s1=Y.define();function _e(i,e,t){let n=i.facet(J0);n.length?n[0](e):window.onerror&&window.onerror(String(e),t,void 0,void 0,e)||(t?console.error(t+":",e):console.error(e))}var xi=R.define({combine:i=>i.length?i[0]:!0}),Jv=0,Yr=R.define({combine(i){return i.filter((e,t)=>{for(let n=0;n<t;n++)if(i[n].plugin==e.plugin)return!1;return!0})}}),se=class i{constructor(e,t,n,r,s){this.id=e,this.create=t,this.domEventHandlers=n,this.domEventObservers=r,this.baseExtensions=s(this),this.extension=this.baseExtensions.concat(Yr.of({plugin:this,arg:void 0}))}of(e){return this.baseExtensions.concat(Yr.of({plugin:this,arg:e}))}static define(e,t){let{eventHandlers:n,eventObservers:r,provide:s,decorations:o}=t||{};return new i(Jv++,e,n,r,l=>{let a=[];return o&&a.push(Ka.of(c=>{let h=c.plugin(l);return h?o(h):L.none})),s&&a.push(s(l)),a})}static fromClass(e,t){return i.define((n,r)=>new e(n,r),t)}},co=class{constructor(e){this.spec=e,this.mustUpdate=null,this.value=null}get plugin(){return this.spec&&this.spec.plugin}update(e){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(this.mustUpdate=null,this.value.update)try{this.value.update(t)}catch(n){if(_e(t.state,n,"CodeMirror plugin crashed"),this.value.destroy)try{this.value.destroy()}catch{}this.deactivate()}}}else if(this.spec)try{this.value=this.spec.plugin.create(e,this.spec.arg)}catch(t){_e(e.state,t,"CodeMirror plugin crashed"),this.deactivate()}return this}destroy(e){var t;if(!((t=this.value)===null||t===void 0)&&t.destroy)try{this.value.destroy()}catch(n){_e(e.state,n,"CodeMirror plugin crashed")}}deactivate(){this.spec=this.value=null}},o1=R.define(),Fu=R.define(),Ka=R.define(),l1=R.define(),Hu=R.define(),So=R.define(),a1=R.define();function n0(i,e){let t=i.state.facet(a1);if(!t.length)return t;let n=t.map(s=>s instanceof Function?s(i):s),r=[];return G.spans(n,e.from,e.to,{point(){},span(s,o,l,a){let c=s-e.from,h=o-e.from,f=r;for(let u=l.length-1;u>=0;u--,a--){let d=l[u].spec.bidiIsolate,p;if(d==null&&(d=Kv(e.text,c,h)),a>0&&f.length&&(p=f[f.length-1]).to==c&&p.direction==d)p.to=h,f=p.inner;else{let O={from:c,to:h,direction:d,inner:[]};f.push(O),f=O.inner}}}}),r}var c1=R.define();function Ku(i){let e=0,t=0,n=0,r=0;for(let s of i.state.facet(c1)){let o=s(i);o&&(o.left!=null&&(e=Math.max(e,o.left)),o.right!=null&&(t=Math.max(t,o.right)),o.top!=null&&(n=Math.max(n,o.top)),o.bottom!=null&&(r=Math.max(r,o.bottom)))}return{left:e,right:t,top:n,bottom:r}}var no=R.define(),It=class i{constructor(e,t,n,r){this.fromA=e,this.toA=t,this.fromB=n,this.toB=r}join(e){return new i(Math.min(this.fromA,e.fromA),Math.max(this.toA,e.toA),Math.min(this.fromB,e.fromB),Math.max(this.toB,e.toB))}addToSet(e){let t=e.length,n=this;for(;t>0;t--){let r=e[t-1];if(!(r.fromA>n.toA)){if(r.toA<n.fromA)break;n=n.join(r),e.splice(t-1,1)}}return e.splice(t,0,n),e}static extendWithRanges(e,t){if(t.length==0)return e;let n=[];for(let r=0,s=0,o=0;;){let l=r<e.length?e[r].fromB:1e9,a=s<t.length?t[s]:1e9,c=Math.min(l,a);if(c==1e9)break;let h=c+o,f=c,u=h;for(;;)if(s<t.length&&t[s]<=f){let d=t[s+1];s+=2,f=Math.max(f,d);for(let p=r;p<e.length&&e[p].fromB<=f;p++)o=e[p].toA-e[p].toB;u=Math.max(u,d+o)}else if(r<e.length&&e[r].fromB<=f){let d=e[r++];f=Math.max(f,d.toB),u=Math.max(u,d.toA),o=d.toA-d.toB}else break;n.push(new i(h,u,c,f))}return n}},La=class i{constructor(e,t,n){this.view=e,this.state=t,this.transactions=n,this.flags=0,this.startState=e.state,this.changes=He.empty(this.startState.doc.length);for(let s of n)this.changes=this.changes.compose(s.changes);let r=[];this.changes.iterChangedRanges((s,o,l,a)=>r.push(new It(s,o,l,a))),this.changedRanges=r}static create(e,t,n){return new i(e,t,n)}get viewportChanged(){return(this.flags&4)>0}get viewportMoved(){return(this.flags&8)>0}get heightChanged(){return(this.flags&2)>0}get geometryChanged(){return this.docChanged||(this.flags&18)>0}get focusChanged(){return(this.flags&1)>0}get docChanged(){return!this.changes.empty}get selectionSet(){return this.transactions.some(e=>e.selection)}get empty(){return this.flags==0&&this.transactions.length==0}},e$=[],fe=class{constructor(e,t,n=0){this.dom=e,this.length=t,this.flags=n,this.parent=null,e.cmTile=this}get breakAfter(){return this.flags&1}get children(){return e$}isWidget(){return!1}get isHidden(){return!1}isComposite(){return!1}isLine(){return!1}isText(){return!1}isBlock(){return!1}get domAttrs(){return null}sync(e){if(this.flags|=2,this.flags&4){this.flags&=-5;let t=this.domAttrs;t&&Lv(this.dom,t)}}toString(){return this.constructor.name+(this.children.length?`(${this.children})`:"")+(this.breakAfter?"#":"")}destroy(){this.parent=null}setDOM(e){this.dom=e,e.cmTile=this}get posAtStart(){return this.parent?this.parent.posBefore(this):0}get posAtEnd(){return this.posAtStart+this.length}posBefore(e,t=this.posAtStart){let n=t;for(let r of this.children){if(r==e)return n;n+=r.length+r.breakAfter}throw new RangeError("Invalid child in posBefore")}posAfter(e){return this.posBefore(e)+e.length}covers(e){return!0}coordsIn(e,t,n){return null}domPosFor(e,t){let n=Bi(this.dom),r=this.length?e>0:t>0;return new li(this.parent.dom,n+(r?1:0),e==0||e==this.length)}markDirty(e){this.flags&=-3,e&&(this.flags|=4),this.parent&&this.parent.flags&2&&this.parent.markDirty(!1)}get overrideDOMText(){return null}get root(){for(let e=this;e;e=e.parent)if(e instanceof Hr)return e;return null}static get(e){return e.cmTile}},Fr=class extends fe{constructor(e){super(e,0),this._children=[]}isComposite(){return!0}get children(){return this._children}get lastChild(){return this.children.length?this.children[this.children.length-1]:null}append(e){this.children.push(e),e.parent=this}sync(e){if(this.flags&2)return;super.sync(e);let t=this.dom,n=null,r,s=e?.node==t?e:null,o=0;for(let l of this.children){if(l.sync(e),o+=l.length+l.breakAfter,r=n?n.nextSibling:t.firstChild,s&&r!=l.dom&&(s.written=!0),l.dom.parentNode==t)for(;r&&r!=l.dom;)r=r0(r);else t.insertBefore(l.dom,r);n=l.dom}for(r=n?n.nextSibling:t.firstChild,s&&r&&(s.written=!0);r;)r=r0(r);this.length=o}};function r0(i){let e=i.nextSibling;return i.parentNode.removeChild(i),e}var Hr=class extends Fr{constructor(e,t){super(t),this.view=e}owns(e){for(;e;e=e.parent)if(e==this)return!0;return!1}isBlock(){return!0}nearest(e){for(;;){if(!e)return null;let t=fe.get(e);if(t&&this.owns(t))return t;e=e.parentNode}}blockTiles(e){for(let t=[],n=this,r=0,s=0;;)if(r==n.children.length){if(!t.length)return;n=n.parent,n.breakAfter&&s++,r=t.pop()}else{let o=n.children[r++];if(o instanceof wi)t.push(r),n=o,r=0;else{let l=s+o.length,a=e(o,s);if(a!==void 0)return a;s=l+o.breakAfter}}}resolveBlock(e,t){let n,r=-1,s,o=-1;if(this.blockTiles((l,a)=>{let c=a+l.length;if(e>=a&&e<=c){if(l.isWidget()&&t>=-1&&t<=1){if(l.flags&32)return!0;l.flags&16&&(n=void 0)}(a<e||e==c&&(t<-1?l.length:l.covers(1)))&&(!n||!l.isWidget()&&n.isWidget())&&(n=l,r=e-a),(c>e||e==a&&(t>1?l.length:l.covers(-1)))&&(!s||!l.isWidget()&&s.isWidget())&&(s=l,o=e-a)}}),!n&&!s)throw new Error("No tile at position "+e);return n&&t<0||!s?{tile:n,offset:r}:{tile:s,offset:o}}},wi=class i extends Fr{constructor(e,t){super(e),this.wrapper=t}isBlock(){return!0}covers(e){return this.children.length?e<0?this.children[0].covers(-1):this.lastChild.covers(1):!1}get domAttrs(){return this.wrapper.attributes}static of(e,t){let n=new i(t||document.createElement(e.tagName),e);return t||(n.flags|=4),n}},Kr=class i extends Fr{constructor(e,t){super(e),this.attrs=t}isLine(){return!0}static start(e,t,n){let r=new i(t||document.createElement("div"),e);return(!t||!n)&&(r.flags|=4),r}get domAttrs(){return this.attrs}resolveInline(e,t,n){let r=null,s=-1,o=null,l=-1;function a(h,f){for(let u=0,d=0;u<h.children.length&&d<=f;u++){let p=h.children[u],O=d+p.length;O>=f&&(p.isComposite()?a(p,f-d):(!o||o.isHidden&&(t>0&&!(o.flags&32)||n&&i$(o,p)))&&(O>f||p.flags&32&&t<=1)?(o=p,l=f-d):(d<f||p.flags&16&&!p.isHidden&&t>=-1)&&(r=p,s=f-d)),d=O}}a(this,e);let c=(t<0?r:o)||r||o;return c?{tile:c,offset:c==r?s:l}:null}coordsIn(e,t,n){let r=this.resolveInline(e,t,!0);return r?r.tile.coordsIn(Math.max(0,r.offset),t,n):t$(this)}domIn(e,t){let n=this.resolveInline(e,t);if(n){let{tile:r,offset:s}=n;if(this.dom.contains(r.dom))return r.isText()?new li(r.dom,Math.min(r.dom.nodeValue.length,s)):r.domPosFor(s,r.flags&16?1:r.flags&32?-1:t);let o=n.tile.parent,l=!1;for(let a of o.children){if(l)return new li(a.dom,0);a==n.tile&&(l=!0)}}return new li(this.dom,0)}};function t$(i){let e=i.dom.lastChild;if(!e)return i.dom.getBoundingClientRect();let t=Pa(e);return t[t.length-1]||null}function i$(i,e){let t=i.coordsIn(0,1),n=e.coordsIn(0,1);return t&&n&&n.top<t.bottom}var ct=class i extends Fr{constructor(e,t){super(e),this.mark=t}get domAttrs(){return this.mark.attrs}static of(e,t){let n=new i(t||document.createElement(e.tagName),e);return t||(n.flags|=4),n}},Dn=class i extends fe{constructor(e,t){super(e,t.length),this.text=t}sync(e){this.flags&2||(super.sync(e),this.dom.nodeValue!=this.text&&(e&&e.node==this.dom&&(e.written=!0),this.dom.nodeValue=this.text))}isText(){return!0}toString(){return JSON.stringify(this.text)}coordsIn(e,t,n){let r=this.dom.nodeValue.length;e>r&&(e=r);let s=e,o=e,l=0;e==0&&t<0||e==r&&t>=0?_.chrome||_.gecko||(e?(s--,l=1):o<r&&(o++,l=-1)):t<0?s--:o<r&&o++;let a=yo(this.dom,s,o).getClientRects();if(!a.length)return null;let c=a[(l?l<0:t>=0)?0:a.length-1];return _.safari&&!l&&c.width==0&&(c=Array.prototype.find.call(a,h=>h.width)||c),n==null?c:_a(c,(l?l>0:t<0)==n)}static of(e,t){let n=new i(t||document.createTextNode(e),e);return t||(n.flags|=2),n}},Bn=class i extends fe{constructor(e,t,n,r){super(e,t,r),this.widget=n}isWidget(){return!0}get isHidden(){return this.widget.isHidden}covers(e){return this.flags&48?!1:(this.flags&(e<0?64:128))>0}coordsIn(e,t){return this.coordsInWidget(e,t,!1)}coordsInWidget(e,t,n){let r=this.widget.coordsAt(this.dom,e,t);if(r)return r;if(n)return _a(this.dom.getBoundingClientRect(),this.length?e==0:t<=0);{let s=this.dom.getClientRects(),o=null;if(!s.length)return null;let l=this.flags&16?!0:this.flags&32?!1:e>0;for(let a=l?s.length-1:0;o=s[a],!(e>0?a==0:a==s.length-1||o.top<o.bottom);a+=l?-1:1);return _a(o,!l)}}get overrideDOMText(){if(!this.length)return q.empty;let{root:e}=this;if(!e)return q.empty;let t=this.posAtStart;return e.view.state.doc.slice(t,t+this.length)}destroy(){super.destroy(),this.widget.destroy(this.dom)}static of(e,t,n,r,s){return s||(s=e.toDOM(t),e.editable||(s.contentEditable="false")),new i(s,n,e,r)}},Jr=class extends fe{constructor(e){let t=document.createElement("img");t.className="cm-widgetBuffer",t.setAttribute("aria-hidden","true"),super(t,0,e)}get isHidden(){return!0}get overrideDOMText(){return q.empty}coordsIn(e,t,n){let r=this.dom.getBoundingClientRect();return n==null?r:_a(r,t>0==n)}},ou=class{constructor(e){this.index=0,this.beforeBreak=!1,this.parents=[],this.tile=e}advance(e,t,n){let{tile:r,index:s,beforeBreak:o,parents:l}=this;for(;e||t>0;)if(r.isComposite())if(o){if(!e)break;n&&n.break(),e--,o=!1}else if(s==r.children.length){if(!e&&!l.length)break;n&&n.leave(r),o=!!r.breakAfter,{tile:r,index:s}=l.pop(),s++}else{let a=r.children[s],c=a.breakAfter;(t>0?a.length<=e:a.length<e)&&(!n||n.skip(a,0,a.length)!==!1||!a.isComposite)?(o=!!c,s++,e-=a.length):(l.push({tile:r,index:s}),r=a,s=0,n&&a.isComposite()&&n.enter(a))}else{let a=r.length;if(s<a&&e){let c=Math.min(e,a-s);n&&n.skip(r,s,s+c),e-=c,s+=c}if(s==a)o=!!r.breakAfter,{tile:r,index:s}=l.pop(),s++;else if(!e)break}return this.tile=r,this.index=s,this.beforeBreak=o,this}get root(){return this.parents.length?this.parents[0].tile:this.tile}},lu=class{constructor(e,t,n,r){this.from=e,this.to=t,this.wrapper=n,this.rank=r}},au=class{constructor(e,t,n){this.cache=e,this.root=t,this.blockWrappers=n,this.curLine=null,this.lastBlock=null,this.afterWidget=null,this.pos=0,this.wrappers=[],this.wrapperPos=0}addText(e,t,n,r){var s;this.flushBuffer();let o=this.ensureMarks(t,n),l=o.lastChild;if(l&&l.isText()&&!(l.flags&8)&&l.length+e.length<512){this.cache.reused.set(l,2);let a=o.children[o.children.length-1]=new Dn(l.dom,l.text+e);a.parent=o}else o.append(r||Dn.of(e,(s=this.cache.find(Dn))===null||s===void 0?void 0:s.dom));this.pos+=e.length,this.afterWidget=null}addComposition(e,t){let n=this.curLine;n.dom!=t.line.dom&&(n.setDOM(this.cache.reused.has(t.line)?Vf(t.line.dom):t.line.dom),this.cache.reused.set(t.line,2));let r=n;for(let l=t.marks.length-1;l>=0;l--){let a=t.marks[l],c=r.lastChild;if(c instanceof ct&&c.mark.eq(a.mark))c.dom!=a.dom&&c.setDOM(Vf(a.dom)),r=c;else{let{dom:h}=a;this.cache.reused.get(a)&&fe.get(a.dom)&&(h=Vf(a.dom));let f=ct.of(a.mark,h);r.append(f),r=f}this.cache.reused.set(a,2)}let s=fe.get(e.text);s&&this.cache.reused.set(s,2);let o=new Dn(e.text,e.text.nodeValue);o.flags|=8,this.pos=e.range.toB,r.append(o)}addInlineWidget(e,t,n){let r=this.afterWidget&&e.flags&48&&(this.afterWidget.flags&48)==(e.flags&48);r||this.flushBuffer();let s=this.ensureMarks(t,n);!r&&!(e.flags&16)&&s.append(this.getBuffer(1)),s.append(e),this.pos+=e.length,this.afterWidget=e}addMark(e,t,n){this.flushBuffer(),this.ensureMarks(t,n).append(e),this.pos+=e.length,this.afterWidget=null}addBlockWidget(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}continueWidget(e){let t=this.afterWidget||this.lastBlock;t.length+=e,this.pos+=e}addLineStart(e,t){var n;e||(e=h1);let r=Kr.start(e,t||((n=this.cache.find(Kr))===null||n===void 0?void 0:n.dom),!!t);this.getBlockPos().append(this.lastBlock=this.curLine=r)}addLine(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}addBreak(){this.lastBlock.flags|=1,this.endLine(),this.pos++}addLineStartIfNotCovered(e){this.blockPosCovered()||this.addLineStart(e)}ensureLine(e){this.curLine||this.addLineStart(e)}ensureMarks(e,t){var n;let r=this.curLine;for(let s=e.length-1;s>=0;s--){let o=e[s],l;if(t>0&&(l=r.lastChild)&&l instanceof ct&&l.mark.eq(o))r=l,t--;else{let a=ct.of(o,(n=this.cache.find(ct,c=>c.mark.eq(o)))===null||n===void 0?void 0:n.dom);r.append(a),r=a,t=0}}return r}endLine(){if(this.curLine){this.flushBuffer();let e=this.curLine.lastChild;(!e||!s0(this.curLine,!1)||e.dom.nodeName!="BR"&&e.isWidget()&&!(_.ios&&s0(this.curLine,!0)))&&this.curLine.append(this.cache.findWidget(Yf,0,32)||new Bn(Yf.toDOM(),0,Yf,32)),this.curLine=this.afterWidget=null}}updateBlockWrappers(){this.wrapperPos>this.pos+1e4&&(this.blockWrappers.goto(this.pos),this.wrappers.length=0);for(let e=this.wrappers.length-1;e>=0;e--)this.wrappers[e].to<this.pos&&this.wrappers.splice(e,1);for(let e=this.blockWrappers;e.value&&e.from<=this.pos;e.next())if(e.to>=this.pos){let t=e.rank*102+e.value.rank,n=new lu(e.from,e.to,e.value,t),r=this.wrappers.length;for(;r>0&&(this.wrappers[r-1].rank-n.rank||this.wrappers[r-1].to-n.to)<0;)r--;this.wrappers.splice(r,0,n)}this.wrapperPos=this.pos}getBlockPos(){var e;this.updateBlockWrappers();let t=this.root;for(let n of this.wrappers){let r=t.lastChild;if(n.from<this.pos&&r instanceof wi&&r.wrapper.eq(n.wrapper))t=r;else{let s=wi.of(n.wrapper,(e=this.cache.find(wi,o=>o.wrapper.eq(n.wrapper)))===null||e===void 0?void 0:e.dom);t.append(s),t=s}}return t}blockPosCovered(){let e=this.lastBlock;return e!=null&&!e.breakAfter&&(!e.isWidget()||(e.flags&160)>0)}getBuffer(e){let t=2|(e<0?16:32),n=this.cache.find(Jr,void 0,1);return n&&(n.flags=t),n||new Jr(t)}flushBuffer(){this.afterWidget&&!(this.afterWidget.flags&32)&&(this.afterWidget.parent.append(this.getBuffer(-1)),this.afterWidget=null)}},cu=class{constructor(e){this.skipCount=0,this.text="",this.textOff=0,this.cursor=e.iter()}skip(e){this.textOff+e<=this.text.length?this.textOff+=e:(this.skipCount+=e-(this.text.length-this.textOff),this.text="",this.textOff=0)}next(e){if(this.textOff==this.text.length){let{value:r,lineBreak:s,done:o}=this.cursor.next(this.skipCount);if(this.skipCount=0,o)throw new Error("Ran out of text content when drawing inline views");this.text=r;let l=this.textOff=Math.min(e,r.length);return s?null:r.slice(0,l)}let t=Math.min(this.text.length,this.textOff+e),n=this.text.slice(this.textOff,t);return this.textOff=t,n}},Da=[Bn,Kr,Dn,ct,Jr,wi,Hr];for(let i=0;i<Da.length;i++)Da[i].bucket=i;var hu=class{constructor(e){this.view=e,this.buckets=Da.map(()=>[]),this.index=Da.map(()=>0),this.reused=new Map}add(e){let t=e.constructor.bucket,n=this.buckets[t];n.length<6?n.push(e):n[this.index[t]=(this.index[t]+1)%6]=e}find(e,t,n=2){let r=e.bucket,s=this.buckets[r],o=this.index[r];for(let l=0;l<s.length;l++){let a=(l+o)%s.length,c=s[a];if((!t||t(c))&&!this.reused.has(c))return s.splice(a,1),a<o&&this.index[r]--,this.reused.set(c,n),c}return null}findWidget(e,t,n){let r=this.buckets[0];if(r.length)for(let s=0,o=0;;s++){if(s==r.length){if(o)return null;o=1,s=0}let l=r[s];if(!this.reused.has(l)&&(o==0?l.widget.compare(e):l.widget.constructor==e.constructor&&e.updateDOM(l.dom,this.view,l.widget)))return r.splice(s,1),s<this.index[0]&&this.index[0]--,l.widget==e&&l.length==t&&(l.flags&497)==n?(this.reused.set(l,1),l):(this.reused.set(l,2),new Bn(l.dom,t,e,l.flags&-498|n))}}reuse(e){return this.reused.set(e,1),e}maybeReuse(e,t=2){if(!this.reused.has(e))return this.reused.set(e,t),e.dom}clear(){for(let e=0;e<this.buckets.length;e++)this.buckets[e].length=this.index[e]=0}},fu=class{constructor(e,t,n,r,s){this.view=e,this.decorations=r,this.disallowBlockEffectsFor=s,this.openWidget=!1,this.openMarks=0,this.cache=new hu(e),this.text=new cu(e.state.doc),this.builder=new au(this.cache,new Hr(e,e.contentDOM),G.iter(n)),this.cache.reused.set(t,2),this.old=new ou(t),this.reuseWalker={skip:(o,l,a)=>{if(this.cache.add(o),o.isComposite())return!1},enter:o=>this.cache.add(o),leave:()=>{},break:()=>{}}}run(e,t){let n=t&&this.getCompositionContext(t.text);for(let r=0,s=0,o=0;;){let l=o<e.length?e[o++]:null,a=l?l.fromA:this.old.root.length;if(a>r){let c=a-r;this.preserve(c,!o,!l),r=a,s+=c}if(!l)break;t&&l.fromA<=t.range.fromA&&l.toA>=t.range.toA?(this.forward(l.fromA,t.range.fromA,t.range.fromA<t.range.toA?1:-1),this.emit(s,t.range.fromB),this.builder.flushBuffer(),this.cache.clear(),this.builder.addComposition(t,n),this.text.skip(t.range.toB-t.range.fromB),this.forward(t.range.fromA,l.toA),this.emit(t.range.toB,l.toB)):(this.forward(l.fromA,l.toA),this.emit(s,l.toB)),s=l.toB,r=l.toA}return this.builder.curLine&&this.builder.endLine(),this.builder.root}preserve(e,t,n){let r=s$(this.old),s=this.openMarks;this.old.advance(e,n?1:-1,{skip:(o,l,a)=>{if(o.isWidget())if(this.openWidget)this.builder.continueWidget(a-l);else{let c=a>0||l<o.length?Bn.of(o.widget,this.view,a-l,o.flags&496,this.cache.maybeReuse(o)):this.cache.reuse(o);c.flags&256?(c.flags&=-2,this.builder.addBlockWidget(c)):(this.builder.ensureLine(null),this.builder.addInlineWidget(c,r,s),s=r.length)}else if(o.isText())this.builder.ensureLine(null),!l&&a==o.length&&!this.cache.reused.has(o)?this.builder.addText(o.text,r,s,this.cache.reuse(o)):(this.cache.add(o),this.builder.addText(o.text.slice(l,a),r,s)),s=r.length;else if(o.isLine())o.flags&=-2,this.cache.reused.set(o,1),this.builder.addLine(o);else if(o instanceof Jr)this.cache.add(o);else if(o instanceof ct)this.builder.ensureLine(null),this.builder.addMark(o,r,s),this.cache.reused.set(o,1),s=r.length;else return!1;this.openWidget=!1},enter:o=>{o.isLine()?this.builder.addLineStart(o.attrs,this.cache.maybeReuse(o)):(this.cache.add(o),o instanceof ct&&r.unshift(o.mark)),this.openWidget=!1},leave:o=>{o.isLine()?r.length&&(r.length=s=0):o instanceof ct&&(r.shift(),s=Math.min(s,r.length))},break:()=>{this.builder.addBreak(),this.openWidget=!1}}),this.text.skip(e)}emit(e,t){let n=null,r=this.builder,s=-1,o=G.spans(this.decorations,e,t,{point:(l,a,c,h,f,u)=>{if(c instanceof In){if(this.disallowBlockEffectsFor[u]){if(c.block)throw new RangeError("Block decorations may not be specified via plugins");if(a>this.view.state.doc.lineAt(l).to)throw new RangeError("Decorations that replace line breaks may not be specified via plugins")}if(s=h.length,f>h.length)r.continueWidget(a-l);else{let d=c.widget||(c.block?Vi.block:Vi.inline),p=n$(c),O=this.cache.findWidget(d,a-l,p)||Bn.of(d,this.view,a-l,p);c.block?(c.startSide>0&&r.addLineStartIfNotCovered(n),r.addBlockWidget(O)):(r.ensureLine(n),r.addInlineWidget(O,h,f))}n=null}else n=r$(n,c);a>l&&this.text.skip(a-l)},span:(l,a,c,h)=>{for(let f=l;f<a;){let u=this.text.next(Math.min(512,a-f));u==null?(r.addLineStartIfNotCovered(n),r.addBreak(),f++):(r.ensureLine(n),r.addText(u,c,f==l?h:c.length),f+=u.length),n=null}s=c.length}});s>-1&&(this.openWidget=o>s),this.openWidget||r.addLineStartIfNotCovered(n),this.openMarks=o}forward(e,t,n=1){t-e<=10?this.old.advance(t-e,n,this.reuseWalker):(this.old.advance(5,-1,this.reuseWalker),this.old.advance(t-e-10,-1),this.old.advance(5,n,this.reuseWalker))}getCompositionContext(e){let t=[],n=null;for(let r=e.parentNode;;r=r.parentNode){let s=fe.get(r);if(r==this.view.contentDOM)break;s instanceof ct?t.push(s):s?.isLine()?n=s:s instanceof wi||(r.nodeName=="DIV"&&!n?n=new Kr(r,h1):n||t.push(ct.of(new go({tagName:r.nodeName.toLowerCase(),attributes:Dv(r)}),r)))}return n?{line:n,marks:t}:null}};function s0(i,e){let t=n=>{for(let r of n.children)if((e?r.isText():r.length)||t(r))return!0;return!1};return t(i)}function n$(i){let e=i.isReplace?(i.startSide<0?64:0)|(i.endSide>0?128:0):i.startSide>0?32:16;return i.block&&(e|=256),e}var h1={class:"cm-line"};function r$(i,e){let t=e.spec.attributes,n=e.spec.class;return!t&&!n||(i||(i={class:"cm-line"}),t&&zu(t,i),n&&(i.class+=" "+n)),i}function s$(i){let e=[];for(let t=i.parents.length;t>1;t--){let n=t==i.parents.length?i.tile:i.parents[t].tile;n instanceof ct&&e.push(n.mark)}return e}function Vf(i){let e=fe.get(i);return e&&e.setDOM(i.cloneNode()),i}var Vi=class extends De{constructor(e){super(),this.tag=e}eq(e){return e.tag==this.tag}toDOM(){return document.createElement(this.tag)}updateDOM(e){return e.nodeName.toLowerCase()==this.tag}get isHidden(){return!0}};Vi.inline=new Vi("span");Vi.block=new Vi("div");var Yf=new class extends De{toDOM(){return document.createElement("br")}get isHidden(){return!0}get editable(){return!0}},ja=class{constructor(e){this.view=e,this.decorations=[],this.blockWrappers=[],this.dynamicDecorationMap=[!1],this.domChanged=null,this.hasComposition=null,this.editContextFormatting=L.none,this.lastCompositionAfterCursor=!1,this.minWidth=0,this.minWidthFrom=0,this.minWidthTo=0,this.impreciseAnchor=null,this.impreciseHead=null,this.forceSelection=!1,this.lastUpdate=Date.now(),this.updateDeco(),this.tile=new Hr(e,e.contentDOM),this.updateInner([new It(0,0,0,e.state.doc.length)],null)}update(e){var t;let n=e.changedRanges;this.minWidth>0&&n.length&&(n.every(({fromA:h,toA:f})=>f<this.minWidthFrom||h>this.minWidthTo)?(this.minWidthFrom=e.changes.mapPos(this.minWidthFrom,1),this.minWidthTo=e.changes.mapPos(this.minWidthTo,1)):this.minWidth=this.minWidthFrom=this.minWidthTo=0),this.updateEditContextFormatting(e);let r=-1;this.view.inputState.composing>=0&&!this.view.observer.editContext&&(!((t=this.domChanged)===null||t===void 0)&&t.newSel?r=this.domChanged.newSel.head:!d$(e.changes,this.hasComposition)&&!e.selectionSet&&(r=e.state.selection.main.head));let s=r>-1?l$(this.view,e.changes,r):null;if(this.domChanged=null,this.hasComposition){let{from:h,to:f}=this.hasComposition;n=new It(h,f,e.changes.mapPos(h,-1),e.changes.mapPos(f,1)).addToSet(n.slice())}this.hasComposition=s?{from:s.range.fromB,to:s.range.toB}:null,(_.ie||_.chrome)&&!s&&e&&e.state.doc.lines!=e.startState.doc.lines&&(this.forceSelection=!0);let o=this.decorations,l=this.blockWrappers;this.updateDeco();let a=h$(o,this.decorations,e.changes);a.length&&(n=It.extendWithRanges(n,a));let c=f$(l,this.blockWrappers,e.changes);return c.length&&(n=It.extendWithRanges(n,c)),s&&!n.some(h=>h.fromA<=s.range.fromA&&h.toA>=s.range.toA)&&(n=s.range.addToSet(n.slice())),this.tile.flags&2&&n.length==0?!1:(this.updateInner(n,s),e.transactions.length&&(this.lastUpdate=Date.now()),!0)}updateInner(e,t){this.view.viewState.mustMeasureContent=!0;let{observer:n}=this.view;n.ignore(()=>{if(t||e.length){let o=this.tile,l=new fu(this.view,o,this.blockWrappers,this.decorations,this.dynamicDecorationMap);t&&fe.get(t.text)&&l.cache.reused.set(fe.get(t.text),2),this.tile=l.run(e,t),uu(o,l.cache.reused)}this.tile.dom.style.height=this.view.viewState.contentHeight/this.view.scaleY+"px",this.tile.dom.style.flexBasis=this.minWidth?this.minWidth+"px":"";let s=_.chrome||_.ios?{node:n.selectionRange.focusNode,written:!1}:void 0;this.tile.sync(s),s&&(s.written||n.selectionRange.focusNode!=s.node||!this.tile.dom.contains(s.node))&&(this.forceSelection=!0),this.tile.dom.style.height=""});let r=[];if(this.view.viewport.from||this.view.viewport.to<this.view.state.doc.length)for(let s of this.tile.children)s.isWidget()&&s.widget instanceof ho&&r.push(s.dom);n.updateGaps(r)}updateEditContextFormatting(e){this.editContextFormatting=this.editContextFormatting.map(e.changes);for(let t of e.transactions)for(let n of t.effects)n.is(s1)&&(this.editContextFormatting=n.value)}updateSelection(e=!1,t=!1){(e||!this.view.observer.selectionRange.focusNode)&&this.view.observer.readSelectionRange();let{dom:n}=this.tile,r=this.view.root.activeElement,s=r==n,o=!s&&!(this.view.state.facet(xi)||n.tabIndex>-1)&&oo(n,this.view.observer.selectionRange)&&!(r&&n.contains(r));if(!(s||t||o))return;let l=this.forceSelection;this.forceSelection=!1;let a=this.view.state.selection.main,c,h;if(a.empty?h=c=this.inlineDOMNearPos(a.anchor,a.assoc||1):(h=this.inlineDOMNearPos(a.head,a.head==a.from?1:-1),c=this.inlineDOMNearPos(a.anchor,a.anchor==a.from?1:-1)),_.gecko&&a.empty&&!this.hasComposition&&o$(c)){let u=document.createTextNode("");this.view.observer.ignore(()=>c.node.insertBefore(u,c.node.childNodes[c.offset]||null)),c=h=new li(u,0),l=!0}let f=this.view.observer.selectionRange;(l||!f.focusNode||(!lo(c.node,c.offset,f.anchorNode,f.anchorOffset)||!lo(h.node,h.offset,f.focusNode,f.focusOffset))&&!this.suppressWidgetCursorChange(f,a))&&(this.view.observer.ignore(()=>{_.android&&_.chrome&&n.contains(f.focusNode)&&u$(f.focusNode,n)&&(n.blur(),n.focus({preventScroll:!0}));let u=bo(this.view.root);if(u)if(a.empty){if(_.gecko){let d=a$(c.node,c.offset);if(d&&d!=3){let p=(d==1?V0:Y0)(c.node,c.offset);p&&(c=new li(p.node,p.offset))}}u.collapse(c.node,c.offset),a.bidiLevel!=null&&u.caretBidiLevel!==void 0&&(u.caretBidiLevel=a.bidiLevel)}else if(u.extend){u.collapse(c.node,c.offset);try{u.extend(h.node,h.offset)}catch{}}else{let d=document.createRange();a.anchor>a.head&&([c,h]=[h,c]),d.setEnd(h.node,h.offset),d.setStart(c.node,c.offset),u.removeAllRanges(),u.addRange(d)}o&&this.view.root.activeElement==n&&(n.blur(),r&&r.focus())}),this.view.observer.setSelectionRange(c,h)),this.impreciseAnchor=c.precise?null:new li(f.anchorNode,f.anchorOffset),this.impreciseHead=h.precise?null:new li(f.focusNode,f.focusOffset)}suppressWidgetCursorChange(e,t){return this.hasComposition&&t.empty&&lo(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)&&this.posFromDOM(e.focusNode,e.focusOffset)==t.head}enforceCursorAssoc(){if(this.hasComposition)return;let{view:e}=this,t=e.state.selection.main,n=bo(e.root),{anchorNode:r,anchorOffset:s}=e.observer.selectionRange;if(!n||!t.empty||!t.assoc||!n.modify)return;let o=this.lineAt(t.head,t.assoc);if(!o)return;let l=o.posAtStart;if(t.head==l||t.head==l+o.length)return;let a=this.coordsAt(t.head,-1),c=this.coordsAt(t.head,1);if(!a||!c||a.bottom>c.top)return;let h=this.domAtPos(t.head+t.assoc,t.assoc);n.collapse(h.node,h.offset),n.modify("move",t.assoc<0?"forward":"backward","lineboundary"),e.observer.readSelectionRange();let f=e.observer.selectionRange;e.docView.posFromDOM(f.anchorNode,f.anchorOffset)!=t.from&&n.collapse(r,s)}posFromDOM(e,t){let n=this.tile.nearest(e);if(!n)return this.tile.dom.compareDocumentPosition(e)&2?0:this.view.state.doc.length;let r=n.posAtStart;if(n.isComposite()){let s;if(e==n.dom)s=n.dom.childNodes[t];else{let o=ki(e)==0?0:t==0?-1:1;for(;;){let l=e.parentNode;if(l==n.dom)break;o==0&&l.firstChild!=l.lastChild&&(e==l.firstChild?o=-1:o=1),e=l}o<0?s=e:s=e.nextSibling}if(s==n.dom.firstChild)return r;for(;s&&!fe.get(s);)s=s.nextSibling;if(!s)return r+n.length;for(let o=0,l=r;;o++){let a=n.children[o];if(a.dom==s)return l;l+=a.length+a.breakAfter}}else return n.isText()?e==n.dom?r+t:r+(t?n.length:0):r}domAtPos(e,t){let{tile:n,offset:r}=this.tile.resolveBlock(e,t);return n.isWidget()?n.domPosFor(r,t):n.domIn(r,t)}inlineDOMNearPos(e,t){let n,r=-1,s=!1,o,l=-1,a=!1;return this.tile.blockTiles((c,h)=>{if(c.isWidget()){if(c.flags&32&&h>=e)return!0;c.flags&16&&(s=!0)}else{let f=h+c.length;if(h<=e&&(n=c,r=e-h,s=f<e),f>=e&&!o&&(o=c,l=e-h,a=h>e),h>e&&o)return!0}}),!n&&!o?this.domAtPos(e,t):(s&&o?n=null:a&&n&&(o=null),n&&t<0||!o?n.domIn(r,t):o.domIn(l,t))}coordsAt(e,t,n){let{tile:r,offset:s}=this.tile.resolveBlock(e,t);return r.isWidget()?r.widget instanceof ho?null:r.coordsInWidget(s,t,!0):r.coordsIn(s,t,n)}lineAt(e,t){let{tile:n}=this.tile.resolveBlock(e,t);return n.isLine()?n:null}coordsForChar(e){let{tile:t,offset:n}=this.tile.resolveBlock(e,1);if(!t.isLine())return null;function r(s,o){if(s.isComposite())for(let l of s.children){if(l.length>=o){let a=r(l,o);if(a)return a}if(o-=l.length,o<0)break}else if(s.isText()&&o<s.length){let l=be(s.text,o);if(l==o)return null;let a=yo(s.dom,o,l).getClientRects();for(let c=0;c<a.length;c++){let h=a[c];if(c==a.length-1||h.top<h.bottom&&h.left<h.right)return h}}return null}return r(t,n)}measureVisibleLineHeights(e){let t=[],{from:n,to:r}=e,s=this.view.contentDOM.clientWidth,o=s>Math.max(this.view.scrollDOM.clientWidth,this.minWidth)+1,l=-1,a=this.view.textDirection==re.LTR,c=0,h=(f,u,d)=>{for(let p=0;p<f.children.length&&!(u>r);p++){let O=f.children[p],g=u+O.length,b=O.dom.getBoundingClientRect(),{height:w}=b;if(d&&!p&&(c+=b.top-d.top),O instanceof wi)g>n&&h(O,u,b);else if(u>=n&&(c>0&&t.push(-c),t.push(w+c),c=0,o)){let x=O.dom.lastChild,X=x?Pa(x):[];if(X.length){let C=X[X.length-1],T=a?C.right-b.left:b.right-C.left;T>l&&(l=T,this.minWidth=s,this.minWidthFrom=u,this.minWidthTo=g)}}d&&p==f.children.length-1&&(c+=d.bottom-b.bottom),u=g+O.breakAfter}};return h(this.tile,0,null),t}textDirectionAt(e){let{tile:t}=this.tile.resolveBlock(e,1);return getComputedStyle(t.dom).direction=="rtl"?re.RTL:re.LTR}measureTextSize(){let e=this.tile.blockTiles(o=>{if(o.isLine()&&o.children.length&&o.length<=20){let l=0,a;for(let c of o.children){if(!c.isText()||/[^ -~]/.test(c.text))return;let h=Pa(c.dom);if(h.length!=1)return;l+=h[0].width,a=h[0].height}if(l)return{lineHeight:o.dom.getBoundingClientRect().height,charWidth:l/o.length,textHeight:a}}});if(e)return e;let t=document.createElement("div"),n,r,s;return t.className="cm-line",t.style.width="99999px",t.style.position="absolute",t.textContent="abc def ghi jkl mno pqr stu",this.view.observer.ignore(()=>{this.tile.dom.appendChild(t);let o=Pa(t.firstChild)[0];n=t.getBoundingClientRect().height,r=o&&o.width?o.width/27:7,s=o&&o.height?o.height:n,t.remove()}),{lineHeight:n,charWidth:r,textHeight:s}}computeBlockGapDeco(){let e=[],t=this.view.viewState;for(let n=0,r=0;;r++){let s=r==t.viewports.length?null:t.viewports[r],o=s?s.from-1:this.view.state.doc.length;if(o>n){let l=(t.lineBlockAt(o).bottom-t.lineBlockAt(n).top)/this.view.scaleY;e.push(L.replace({widget:new ho(l),block:!0,inclusive:!0,isBlockGap:!0}).range(n,o))}if(!s)break;n=s.to+1}return L.set(e)}updateDeco(){let e=1,t=this.view.state.facet(Ka).map(s=>(this.dynamicDecorationMap[e++]=typeof s=="function")?s(this.view):s),n=!1,r=this.view.state.facet(Hu).map((s,o)=>{let l=typeof s=="function";return l&&(n=!0),l?s(this.view):s});for(r.length&&(this.dynamicDecorationMap[e++]=n,t.push(G.join(r))),this.decorations=[this.editContextFormatting,...t,this.computeBlockGapDeco(),this.view.viewState.lineGapDeco];e<this.decorations.length;)this.dynamicDecorationMap[e++]=!1;this.blockWrappers=this.view.state.facet(l1).map(s=>typeof s=="function"?s(this.view):s)}scrollIntoView(e){if(e.isSnapshot){let c=this.view.viewState.lineBlockAt(e.range.head);this.view.scrollDOM.scrollTop=c.top-e.yMargin,this.view.scrollDOM.scrollLeft=e.xMargin;return}for(let c of this.view.state.facet(r1))try{if(c(this.view,e.range,e))return!0}catch(h){_e(this.view.state,h,"scroll handler")}let{range:t}=e,n=this.coordsAt(t.head,t.assoc||(t.head>t.anchor?-1:1)),r;if(!n)return;!t.empty&&(r=this.coordsAt(t.anchor,t.anchor>t.head?-1:1))&&(n={left:Math.min(n.left,r.left),top:Math.min(n.top,r.top),right:Math.max(n.right,r.right),bottom:Math.max(n.bottom,r.bottom)});let s=Ku(this.view),o={left:n.left-s.left,top:n.top-s.top,right:n.right+s.right,bottom:n.bottom+s.bottom},{offsetWidth:l,offsetHeight:a}=this.view.scrollDOM;if(Uv(this.view.scrollDOM,o,t.head<t.anchor?-1:1,e.x,e.y,Math.max(Math.min(e.xMargin,l),-l),Math.max(Math.min(e.yMargin,a),-a),this.view.textDirection==re.LTR),window.visualViewport&&window.innerHeight-window.visualViewport.height>1&&(n.top>window.visualViewport.offsetTop+window.visualViewport.height||n.bottom<window.visualViewport.offsetTop)){let c=this.view.docView.lineAt(t.head,1);if(c){let h=j0(c.dom);c.dom.scrollIntoView({block:"nearest"}),I0(h,!1)}}}lineHasWidget(e){let t=n=>n.isWidget()||n.children.some(t);return t(this.tile.resolveBlock(e,1).tile)}destroy(){uu(this.tile)}};function uu(i,e){let t=e?.get(i);if(t!=1){t==null&&i.destroy();for(let n of i.children)uu(n,e)}}function o$(i){return i.node.nodeType==1&&i.node.firstChild&&(i.offset==0||i.node.childNodes[i.offset-1].contentEditable=="false")&&(i.offset==i.node.childNodes.length||i.node.childNodes[i.offset].contentEditable=="false")}function f1(i,e){let t=i.observer.selectionRange;if(!t.focusNode)return null;let n=V0(t.focusNode,t.focusOffset),r=Y0(t.focusNode,t.focusOffset),s=n||r;if(r&&n&&r.node!=n.node){let l=fe.get(r.node);if(!l||l.isText()&&l.text!=r.node.nodeValue)s=r;else if(i.docView.lastCompositionAfterCursor){let a=fe.get(n.node);!a||a.isText()&&a.text!=n.node.nodeValue||(s=r)}}if(i.docView.lastCompositionAfterCursor=s!=n,!s)return null;let o=e-s.offset;return{from:o,to:o+s.node.nodeValue.length,node:s.node}}function l$(i,e,t){let n=f1(i,t);if(!n)return null;let{node:r,from:s,to:o}=n,l=r.nodeValue;if(/[\n\r]/.test(l)||i.state.doc.sliceString(n.from,n.to)!=l)return null;let a=e.invertedDesc;return{range:new It(a.mapPos(s),a.mapPos(o),s,o),text:r}}function a$(i,e){return i.nodeType!=1?0:(e&&i.childNodes[e-1].contentEditable=="false"?1:0)|(e<i.childNodes.length&&i.childNodes[e].contentEditable=="false"?2:0)}var c$=class{constructor(){this.changes=[]}compareRange(e,t){qr(e,t,this.changes)}comparePoint(e,t){qr(e,t,this.changes)}boundChange(e){qr(e,e,this.changes)}};function h$(i,e,t){let n=new c$;return G.compare(i,e,t,n),n.changes}var du=class{constructor(){this.changes=[]}compareRange(e,t){qr(e,t,this.changes)}comparePoint(){}boundChange(e){qr(e,e,this.changes)}};function f$(i,e,t){let n=new du;return G.compare(i,e,t,n),n.changes}function u$(i,e){for(let t=i;t&&t!=e;t=t.assignedSlot||t.parentNode)if(t.nodeType==1&&t.contentEditable=="false")return!0;return!1}function d$(i,e){let t=!1;return e&&i.iterChangedRanges((n,r)=>{n<e.to&&r>e.from&&(t=!0)}),t}var ho=class extends De{constructor(e){super(),this.height=e}toDOM(){let e=document.createElement("div");return e.className="cm-gap",this.updateDOM(e),e}eq(e){return e.height==this.height}updateDOM(e){return e.style.height=this.height+"px",!0}get editable(){return!0}get estimatedHeight(){return this.height}ignoreEvent(){return!1}};function p$(i,e,t=1){let n=i.charCategorizer(e),r=i.doc.lineAt(e),s=e-r.from;if(r.length==0)return v.cursor(e);s==0?t=1:s==r.length&&(t=-1);let o=s,l=s;t<0?o=be(r.text,s,!1):l=be(r.text,s);let a=n(r.text.slice(o,l));for(;o>0;){let c=be(r.text,o,!1);if(n(r.text.slice(c,o))!=a)break;o=c}for(;l<r.length;){let c=be(r.text,l);if(n(r.text.slice(l,c))!=a)break;l=c}return v.undirectionalRange(o+r.from,l+r.from)}function O$(i,e,t,n,r){let s=Math.round((n-e.left)*i.defaultCharacterWidth);if(i.lineWrapping&&t.height>i.defaultLineHeight*1.5){let l=i.viewState.heightOracle.textHeight,a=Math.floor((r-t.top-(i.defaultLineHeight-l)*.5)/l);s+=a*i.viewState.heightOracle.lineLength}let o=i.state.sliceDoc(t.from,t.to);return t.from+ga(o,s,i.state.tabSize)}function pu(i,e,t){let n=i.lineBlockAt(e);if(Array.isArray(n.type)){let r;for(let s of n.type){if(s.from>e)break;if(!(s.to<e)){if(s.from<e&&s.to>e)return s;(!r||s.type==Le.Text&&(r.type!=s.type||(t<0?s.from<e:s.to>e)))&&(r=s)}}return r||n}return n}function g$(i,e,t,n){let r=pu(i,e.head,e.assoc||-1),s=!n||r.type!=Le.Text||!(i.lineWrapping||r.widgetLineBreaks)?null:i.coordsAtPos(e.assoc<0&&e.head>r.from?e.head-1:e.head);if(s){let o=i.dom.getBoundingClientRect(),l=i.textDirectionAt(r.from),a=i.posAtCoords({x:t==(l==re.LTR)?o.right-1:o.left+1,y:(s.top+s.bottom)/2});if(a!=null)return v.cursor(a,t?-1:1)}return v.cursor(t?r.to:r.from,t?-1:1)}function o0(i,e,t,n){let r=i.state.doc.lineAt(e.head),s=i.bidiSpans(r),o=i.textDirectionAt(r.from);for(let l=e,a=null;;){let c=Hv(r,s,o,l,t),h=G0;if(!c){if(r.number==(t?i.state.doc.lines:1))return l;h=` +`,r=i.state.doc.line(r.number+(t?1:-1)),s=i.bidiSpans(r),c=t?v.cursor(r.from,-1):v.cursor(r.to,1)}if(a){if(!a(h))return l}else{if(!n)return c;a=n(h)}l=c}}function m$(i,e,t){let n=i.state.charCategorizer(e),r=n(t);return s=>{let o=n(s);return r==ae.Space&&(r=o),r==o}}function b$(i,e,t,n){let r=e.head,s=t?1:-1;if(r==(t?i.state.doc.length:0))return v.cursor(r,e.assoc);let o=e.goalColumn,l,a=i.contentDOM.getBoundingClientRect(),c=i.coordsAtPos(r,e.assoc||((e.empty?t:e.head==e.from)?1:-1)),h=i.documentTop;if(c)o==null&&(o=c.left-a.left),l=s<0?c.top:c.bottom;else{let p=i.viewState.lineBlockAt(r);o==null&&(o=Math.min(a.right-a.left,i.defaultCharacterWidth*(r-p.from))),l=(s<0?p.top:p.bottom)+h}let f=a.left+o,u=i.viewState.heightOracle.textHeight>>1,d=n??u;for(let p=0;;p+=u){let O=l+(d+p)*s,g=Ou(i,{x:f,y:O},!1,s);if(t?O>a.bottom:O<a.top)return v.cursor(g.pos,g.assoc);let b=i.coordsAtPos(g.pos,g.assoc),w=b?(b.top+b.bottom)/2:0;if(!b||(t?w>l:w<l))return v.cursor(g.pos,g.assoc,void 0,o)}}function fo(i,e,t){for(;;){let n=0;for(let r of i)r.between(e-1,e+1,(s,o,l)=>{if(e>s&&e<o){let a=n||t||(e-s<o-e?-1:1);e=a<0?s:o,n=a}});if(!n)return e}}function u1(i,e){let t=null;for(let n=0;n<e.ranges.length;n++){let r=e.ranges[n],s=null;if(r.empty){let o=fo(i,r.from,0);o!=r.from&&(s=v.cursor(o,-1))}else{let o=fo(i,r.from,-1),l=fo(i,r.to,1);(o!=r.from||l!=r.to)&&(r.undirectional?s=v.undirectionalRange(r.from,r.to):s=v.range(r.from==r.anchor?o:l,r.from==r.head?o:l))}s&&(t||(t=e.ranges.slice()),t[n]=s)}return t?v.create(t,e.mainIndex):e}function zf(i,e,t){let n=fo(i.state.facet(So).map(r=>r(i)),t.from,e.head>t.from?-1:1);return n==t.from?t:v.cursor(n,n<t.from?1:-1)}var Mt=class{constructor(e,t){this.pos=e,this.assoc=t}};function Ou(i,e,t,n){let r=i.contentDOM.getBoundingClientRect(),s=r.top+i.viewState.paddingTop,{x:o,y:l}=e,a=l-s,c;for(;;){if(a<0)return new Mt(0,1);if(a>i.viewState.docHeight)return new Mt(i.state.doc.length,-1);if(c=i.elementAtHeight(a),n==null)break;if(c.type==Le.Text){if(n<0?c.to<i.viewport.from:c.from>i.viewport.to)break;let u=i.docView.coordsAt(n<0?c.from:c.to,n>0?-1:1);if(u&&(n<0?u.top<=a+s:u.bottom>=a+s))break}let f=i.viewState.heightOracle.textHeight/2;a=n>0?c.bottom+f:c.top-f}if(i.viewport.from>=c.to||i.viewport.to<=c.from){if(t)return null;if(c.type==Le.Text){let f=O$(i,r,c,o,l);return new Mt(f,f==c.from?1:-1)}}if(c.type!=Le.Text)return a<(c.top+c.bottom)/2?new Mt(c.from,1):new Mt(c.to,-1);let h=i.docView.lineAt(c.from,2);return(!h||h.length!=c.length)&&(h=i.docView.lineAt(c.from,-2)),new gu(i,o,l,i.textDirectionAt(c.from)).scanTile(h,c.from)}var gu=class{constructor(e,t,n,r){this.view=e,this.x=t,this.y=n,this.baseDir=r,this.line=null,this.spans=null}bidiSpansAt(e){return(!this.line||this.line.from>e||this.line.to<e)&&(this.line=this.view.state.doc.lineAt(e),this.spans=this.view.bidiSpans(this.line)),this}baseDirAt(e,t){let{line:n,spans:r}=this.bidiSpansAt(e);return r[Rt.find(r,e-n.from,-1,t)].level==this.baseDir}dirAt(e,t){let{line:n,spans:r}=this.bidiSpansAt(e);return r[Rt.find(r,e-n.from,-1,t)].dir}bidiIn(e,t){let{spans:n,line:r}=this.bidiSpansAt(e);return n.length>1||n.length&&(n[0].level!=this.baseDir||n[0].to+r.from<t)}scan(e,t,n=!1){let r=0,s=e.length-1,o=new Set,l=this.bidiIn(e[0],e[s]),a,c,h=-1,f=1e9,u;e:for(;r<s;){let p=s-r,O=r+s>>1;t:if(o.has(O)){for(let w=1;w<p;w++){let x=O+w;if(x>=s&&(x-=p),!o.has(x)){O=x;break t}}break e}o.add(O);let g=t(O),b=0;if(g)for(let w=0;w<g.length;w++){let x=g[w];if(!(x.width==0&&g.length>1))if(x.bottom<this.y)(!a||a.bottom<x.bottom)&&(a=x),b=1;else if(x.top>this.y)(!c||c.top>x.top)&&(c=x),b=-1;else{let X=x.left>this.x?this.x-x.left:x.right<this.x?this.x-x.right:0,C=Math.abs(X);C<f&&(h=O,f=C,u=x),X&&(b=X<0==(this.baseDir==re.LTR)?-1:1)}}b==-1&&(!l||this.baseDirAt(e[O],1))?s=O:b==1&&(!l||this.baseDirAt(e[O+1],-1))&&(r=O+1)}if(!u){if(!c&&!a)return{i:0,after:!1};let p=a&&(!c||this.y-a.bottom<c.top-this.y)?a:c;return this.y=(p.top+p.bottom)/2,this.scan(e,t,!0)}if(f&&!n){let{top:p,bottom:O}=u;if(a&&a.bottom>(p+p+O)/3)return this.y=a.bottom-1,this.scan(e,t,!0);if(c&&c.top<(p+O+O)/3)return this.y=c.top+1,this.scan(e,t,!0)}let d=(l?this.dirAt(e[h],1):this.baseDir)==re.LTR;return{i:h,after:this.x>(u.left+u.right)/2==d}}scanText(e,t){let n=[];for(let s=0;s<e.length;s=be(e.text,s))n.push(t+s);n.push(t+e.length);let r=this.scan(n,s=>{let o=n[s]-t,l=n[s+1]-t;return yo(e.dom,o,l).getClientRects()});return r.after?new Mt(n[r.i+1],-1):new Mt(n[r.i],1)}scanTile(e,t){if(!e.length)return new Mt(t,1);if(e.children.length==1){let l=e.children[0];if(l.isText())return this.scanText(l,t);if(l.isComposite())return this.scanTile(l,t)}let n=[t];for(let l=0,a=t;l<e.children.length;l++)n.push(a+=e.children[l].length);let r=this.scan(n,l=>{let a=e.children[l];return a.flags&48?null:(a.dom.nodeType==1?a.dom:yo(a.dom,0,a.length)).getClientRects()}),s=e.children[r.i],o=n[r.i];return s.isText()?this.scanText(s,o):s.isComposite()?this.scanTile(s,o):r.after?new Mt(n[r.i+1],-1):new Mt(o,1)}},Vr="\uFFFF",mu=class{constructor(e,t){this.points=e,this.view=t,this.text="",this.lineSeparator=t.state.facet(ne.lineSeparator)}append(e){this.text+=e}lineBreak(){this.text+=Vr}readRange(e,t){if(!e)return this;let n=e.parentNode;for(let r=e;;){this.findPointBefore(n,r);let s=this.text.length;this.readNode(r);let o=fe.get(r),l=r.nextSibling;if(l==t){o?.breakAfter&&!l&&n!=this.view.contentDOM&&this.lineBreak();break}let a=fe.get(l);(o&&a?o.breakAfter:(o?o.breakAfter:Za(r))||Za(l)&&(r.nodeName!="BR"||o?.isWidget())&&this.text.length>s)&&!S$(l,t)&&this.lineBreak(),r=l}return this.findPointBefore(n,t),this}readTextNode(e){let t=e.nodeValue;for(let n of this.points)n.node==e&&(n.pos=this.text.length+Math.min(n.offset,t.length));for(let n=0,r=this.lineSeparator?null:/\r\n?|\n/g;;){let s=-1,o=1,l;if(this.lineSeparator?(s=t.indexOf(this.lineSeparator,n),o=this.lineSeparator.length):(l=r.exec(t))&&(s=l.index,o=l[0].length),this.append(t.slice(n,s<0?t.length:s)),s<0)break;if(this.lineBreak(),o>1)for(let a of this.points)a.node==e&&a.pos>this.text.length&&(a.pos-=o-1);n=s+o}}readNode(e){let t=fe.get(e),n=t&&t.overrideDOMText;if(n!=null){this.findPointInside(e,n.length);for(let r=n.iter();!r.next().done;)r.lineBreak?this.lineBreak():this.append(r.value)}else e.nodeType==3?this.readTextNode(e):e.nodeName=="BR"?e.nextSibling&&this.lineBreak():e.nodeType==1&&this.readRange(e.firstChild,null)}findPointBefore(e,t){for(let n of this.points)n.node==e&&e.childNodes[n.offset]==t&&(n.pos=this.text.length)}findPointInside(e,t){for(let n of this.points)(e.nodeType==3?n.node==e:e.contains(n.node))&&(n.pos=this.text.length+(y$(e,n.node,n.offset)?t:0))}};function y$(i,e,t){for(;;){if(!e||t<ki(e))return!1;if(e==i)return!0;t=Bi(e)+1,e=e.parentNode}}function S$(i,e){let t;for(;!(i==e||!i);i=i.nextSibling){let n=fe.get(i);if(!n?.isWidget())return!1;n&&(t||(t=[])).push(n)}if(t)for(let n of t){let r=n.overrideDOMText;if(r?.length)return!1}return!0}var Ia=class{constructor(e,t){this.node=e,this.offset=t,this.pos=-1}},bu=class{constructor(e,t,n,r){this.typeOver=r,this.bounds=null,this.text="",this.domChanged=t>-1;let{impreciseHead:s,impreciseAnchor:o}=e.docView,l=e.state.selection;if(e.state.readOnly&&t>-1)this.newSel=null;else if(t>-1&&(this.bounds=d1(e.docView.tile,t,n,0))){let a=s||o?[]:w$(e),c=new mu(a,e);c.readRange(this.bounds.startDOM,this.bounds.endDOM),this.text=c.text,this.newSel=k$(a,this.bounds.from)}else{let a=e.observer.selectionRange,c=s&&s.node==a.focusNode&&s.offset==a.focusOffset||!eu(e.contentDOM,a.focusNode)?l.main.head:e.docView.posFromDOM(a.focusNode,a.focusOffset),h=o&&o.node==a.anchorNode&&o.offset==a.anchorOffset||!eu(e.contentDOM,a.anchorNode)?l.main.anchor:e.docView.posFromDOM(a.anchorNode,a.anchorOffset),f=e.viewport;if((_.ios||_.chrome)&&c!=h&&Math.min(c,h)<=l.main.from&&Math.max(c,h)>=l.main.to&&(f.from>0||f.to<e.state.doc.length)){let u=Math.min(c,h),d=Math.max(c,h),p=f.from-u,O=f.to-d;(p==0||p==1||u==0)&&(O==0||O==-1||d==e.state.doc.length)&&(c=0,h=e.state.doc.length)}if(e.inputState.composing>-1&&l.ranges.length>1)this.newSel=l.replaceRange(v.range(h,c));else if(e.lineWrapping&&h==c&&!(l.main.empty&&l.main.head==c)&&e.inputState.lastTouchTime>Date.now()-100){let u=e.coordsAtPos(c,-1),d=0;u&&(d=e.inputState.lastTouchY<=u.bottom?-1:1),this.newSel=v.create([v.cursor(c,d)])}else this.newSel=v.single(h,c)}}};function d1(i,e,t,n){if(i.isComposite()){let r=-1,s=-1,o=-1,l=-1;for(let a=0,c=n,h=n;a<i.children.length;a++){let f=i.children[a],u=c+f.length;if(c<e&&u>t)return d1(f,e,t,c);if(u>=e&&r==-1&&(r=a,s=c),c>t&&f.dom.parentNode==i.dom){o=a,l=h;break}h=u,c=u+f.breakAfter}return{from:s,to:l<0?n+i.length:l,startDOM:(r?i.children[r-1].dom.nextSibling:null)||i.dom.firstChild,endDOM:o<i.children.length&&o>=0?i.children[o].dom:null}}else return i.isText()?{from:n,to:n+i.length,startDOM:i.dom,endDOM:i.dom.nextSibling}:null}function p1(i,e){let t,{newSel:n}=e,{state:r}=i,s=r.selection.main,o=i.inputState.lastKeyTime>Date.now()-100?i.inputState.lastKeyCode:-1;if(e.bounds){let{from:l,to:a}=e.bounds,c=s.from,h=null;(o===8||_.android&&e.text.length<a-l)&&(c=s.to,h="end");let f=r.doc.sliceString(l,a,Vr),u,d;!s.empty&&s.from>=l&&s.to<=a&&(e.typeOver||f!=e.text)&&f.slice(0,s.from-l)==e.text.slice(0,s.from-l)&&f.slice(s.to-l)==e.text.slice(u=e.text.length-(f.length-(s.to-l)))?t={from:s.from,to:s.to,insert:q.of(e.text.slice(s.from-l,u).split(Vr))}:(d=O1(f,e.text,c-l,h))&&(_.chrome&&o==13&&d.toB==d.from+2&&e.text.slice(d.from,d.toB)==Vr+Vr&&d.toB--,t={from:l+d.from,to:l+d.toA,insert:q.of(e.text.slice(d.from,d.toB).split(Vr))})}else n&&(!i.hasFocus&&r.facet(xi)||Ua(n,s))&&(n=null);if(!t&&!n)return!1;if((_.mac||_.android)&&t&&t.from==t.to&&t.from==s.head-1&&/^\. ?$/.test(t.insert.toString())&&i.contentDOM.getAttribute("autocorrect")=="off"?(n&&t.insert.length==2&&(n=v.single(n.main.anchor-1,n.main.head-1)),t={from:t.from,to:t.to,insert:q.of([t.insert.toString().replace("."," ")])}):r.doc.lineAt(s.from).to<s.to&&i.docView.lineHasWidget(s.to)&&i.inputState.insertingTextAt>Date.now()-50?t={from:s.from,to:s.to,insert:r.toText(i.inputState.insertingText)}:_.chrome&&t&&t.from==t.to&&t.from==s.head&&t.insert.toString()==` + `&&i.lineWrapping&&(n&&(n=v.single(n.main.anchor-1,n.main.head-1)),t={from:s.from,to:s.to,insert:q.of([" "])}),t)return Ju(i,t,n,o);if(n&&!Ua(n,s)){let l=!1,a="select";return i.inputState.lastSelectionTime>Date.now()-50&&(i.inputState.lastSelectionOrigin=="select"&&(l=!0),a=i.inputState.lastSelectionOrigin,a=="select.pointer"&&(n=u1(r.facet(So).map(c=>c(i)),n))),i.dispatch({selection:n,scrollIntoView:l,userEvent:a}),!0}else return!1}function Ju(i,e,t,n=-1){if(_.ios&&i.inputState.flushIOSKey(e))return!0;let r=i.state.selection.main;if(_.android&&(e.to==r.to&&(e.from==r.from||e.from==r.from-1&&i.state.sliceDoc(e.from,r.from)==" ")&&e.insert.length==1&&e.insert.lines==2&&Gr(i.contentDOM,"Enter",13)||(e.from==r.from-1&&e.to==r.to&&e.insert.length==0||n==8&&e.insert.length<e.to-e.from&&e.to>r.head)&&Gr(i.contentDOM,"Backspace",8)||e.from==r.from&&e.to==r.to+1&&e.insert.length==0&&Gr(i.contentDOM,"Delete",46)))return!0;let s=e.insert.toString();i.inputState.composing>=0&&i.inputState.composing++;let o,l=()=>o||(o=x$(i,e,t));return i.state.facet(e1).some(a=>a(i,e.from,e.to,s,l))||i.dispatch(l()),!0}function x$(i,e,t){let n,r=i.state,s=r.selection.main,o=-1;if(e.from==e.to&&e.from<s.from||e.from>s.to){let a=e.from<s.from?-1:1,c=a<0?s.from:s.to,h=fo(r.facet(So).map(f=>f(i)),c,a);e.from==h&&(o=h)}if(o>-1)n={changes:e,selection:v.cursor(e.from+e.insert.length,-1)};else if(e.from>=s.from&&e.to<=s.to&&e.to-e.from>=(s.to-s.from)/3&&(!t||t.main.empty&&t.main.from==e.from+e.insert.length)&&i.inputState.composing<0){let a=s.from<e.from?r.sliceDoc(s.from,e.from):"",c=s.to>e.to?r.sliceDoc(e.to,s.to):"";n=r.replaceSelection(i.state.toText(a+e.insert.sliceString(0,void 0,i.state.lineBreak)+c))}else{let a=r.changes(e),c=t&&t.main.to<=a.newLength?t.main:void 0;if(r.selection.ranges.length>1&&(i.inputState.composing>=0||i.inputState.compositionPendingChange)&&e.to<=s.to+10&&e.to>=s.to-10){let h=i.state.sliceDoc(e.from,e.to),f,u=t&&f1(i,t.main.head);if(u){let p=e.insert.length-(e.to-e.from);f={from:u.from,to:u.to-p}}else f=i.state.doc.lineAt(s.head);let d=s.to-e.to;n=r.changeByRange(p=>{if(p.from==s.from&&p.to==s.to)return{changes:a,range:c||p.map(a)};let O=p.to-d,g=O-h.length;if(i.state.sliceDoc(g,O)!=h||O>=f.from&&g<=f.to)return{range:p};let b=r.changes({from:g,to:O,insert:e.insert}),w=p.to-s.to;return{changes:b,range:c?v.range(Math.max(0,c.anchor+w),Math.max(0,c.head+w)):p.map(b)}})}else n={changes:a,selection:c&&r.selection.replaceRange(c)}}let l="input.type";return(i.composing||i.inputState.compositionPendingChange&&i.inputState.compositionEndedAt>Date.now()-50)&&(i.inputState.compositionPendingChange=!1,l+=".compose",i.inputState.compositionFirstChange&&(l+=".start",i.inputState.compositionFirstChange=!1)),r.update(n,{userEvent:l,scrollIntoView:!0})}function O1(i,e,t,n){let r=Math.min(i.length,e.length),s=0;for(;s<r&&i.charCodeAt(s)==e.charCodeAt(s);)s++;if(s==r&&i.length==e.length)return null;let o=i.length,l=e.length;for(;o>0&&l>0&&i.charCodeAt(o-1)==e.charCodeAt(l-1);)o--,l--;if(n=="end"){let a=Math.max(0,s-Math.min(o,l));t-=o+a-s}if(o<s&&i.length<e.length){let a=t<=s&&t>=o?s-t:0;s-=a,l=s+(l-o),o=s}else if(l<s){let a=t<=s&&t>=l?s-t:0;s-=a,o=s+(o-l),l=s}return{from:s,toA:o,toB:l}}function w$(i){let e=[];if(i.root.activeElement!=i.contentDOM)return e;let{anchorNode:t,anchorOffset:n,focusNode:r,focusOffset:s}=i.observer.selectionRange;return t&&(e.push(new Ia(t,n)),(r!=t||s!=n)&&e.push(new Ia(r,s))),e}function k$(i,e){if(i.length==0)return null;let t=i[0].pos,n=i.length==2?i[1].pos:t;return t<0||n<0?null:t==n?v.create([v.cursor(n+e,-1)]):v.single(t+e,n+e)}function Ua(i,e){return e.head==i.main.head&&e.anchor==i.main.anchor}var yu=class{setSelectionOrigin(e){this.lastSelectionOrigin=e,this.lastSelectionTime=Date.now()}constructor(e){this.view=e,this.lastKeyCode=0,this.lastKeyTime=0,this.touchActive=!1,this.lastTouchTime=0,this.lastTouchX=0,this.lastTouchY=0,this.lastFocusTime=0,this.lastScrollTop=0,this.lastScrollLeft=0,this.lastWheelEvent=0,this.pendingIOSKey=void 0,this.lastIOSMomentumScroll=0,this.tabFocusMode=-1,this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastContextMenu=0,this.scrollHandlers=[],this.handlers=Object.create(null),this.composing=-1,this.compositionFirstChange=null,this.compositionEndedAt=0,this.compositionPendingKey=!1,this.compositionPendingChange=!1,this.insertingText="",this.insertingTextAt=0,this.mouseSelection=null,this.draggedContent=null,this.handleEvent=this.handleEvent.bind(this),this.notifiedFocused=e.hasFocus,_.safari&&e.contentDOM.addEventListener("input",()=>null),_.gecko&&D$(e.contentDOM.ownerDocument)}handleEvent(e){!M$(this.view,e)||this.ignoreDuringComposition(e)||e.type=="keydown"&&this.keydown(e)||(this.view.updateState!=0?Promise.resolve().then(()=>this.runHandlers(e.type,e)):this.runHandlers(e.type,e))}runHandlers(e,t){let n=this.handlers[e];if(n){for(let r of n.observers)r(this.view,t);for(let r of n.handlers){if(t.defaultPrevented)break;if(r(this.view,t)){t.preventDefault();break}}}}ensureHandlers(e){let t=v$(e),n=this.handlers,r=this.view.contentDOM;for(let s in t)if(s!="scroll"){let o=!t[s].handlers.length,l=n[s];l&&o!=!l.handlers.length&&(r.removeEventListener(s,this.handleEvent),l=null),l||r.addEventListener(s,this.handleEvent,{passive:o})}for(let s in n)s!="scroll"&&!t[s]&&r.removeEventListener(s,this.handleEvent);this.handlers=t}keydown(e){if(this.lastKeyCode=e.keyCode,this.lastKeyTime=Date.now(),e.keyCode==9&&this.tabFocusMode>-1&&(!this.tabFocusMode||Date.now()<=this.tabFocusMode))return!0;if(this.tabFocusMode>0&&e.keyCode!=27&&m1.indexOf(e.keyCode)<0&&(this.tabFocusMode=-1),_.android&&_.chrome&&!e.synthetic&&(e.keyCode==13||e.keyCode==8))return this.view.observer.delayAndroidKey(e.key,e.keyCode),!0;if(_.ios&&!e.synthetic&&!e.altKey&&!e.metaKey&&(g1.some(t=>t.keyCode==e.keyCode)&&!e.ctrlKey||$$.indexOf(e.key)>-1&&e.ctrlKey)){let t={ctrlKey:e.ctrlKey,altKey:e.altKey,metaKey:e.metaKey,shiftKey:e.shiftKey};t.shiftKey&&_.ios&&!/^(off|none)$/.test(this.view.contentDOM.autocapitalize)&&Q$(this.view.win)&&(t.shiftKey=!1);let n=this.pendingIOSKey={key:e.key,keyCode:e.keyCode,mods:t};return setTimeout(()=>{this.pendingIOSKey==n&&this.flushIOSKey()},50),!0}return e.keyCode!=229&&this.view.observer.forceFlush(),!1}flushIOSKey(e){let t=this.pendingIOSKey;return!t||this.view.observer.pendingRecords().length||t.key=="Enter"&&e&&e.from<e.to&&/^\S+$/.test(e.insert.toString())?!1:(this.pendingIOSKey=void 0,Gr(this.view.contentDOM,t.key,t.keyCode,t.mods))}ignoreDuringComposition(e){return!/^key/.test(e.type)||e.synthetic?!1:this.composing>0?!0:_.safari&&!_.ios&&this.compositionPendingKey&&Date.now()-this.compositionEndedAt<100?(this.compositionPendingKey=!1,!0):!1}startMouseSelection(e){this.mouseSelection&&this.mouseSelection.destroy(),this.mouseSelection=e}update(e){this.view.observer.update(e),this.mouseSelection&&this.mouseSelection.update(e),this.draggedContent&&e.docChanged&&(this.draggedContent=this.draggedContent.map(e.changes)),e.transactions.length&&(this.lastKeyCode=this.lastSelectionTime=0)}destroy(){this.mouseSelection&&this.mouseSelection.destroy()}};function Q$(i){return i.visualViewport?i.visualViewport.height*i.visualViewport.scale/i.document.documentElement.clientHeight<.85:!1}function l0(i,e){return(t,n)=>{try{return e.call(i,n,t)}catch(r){_e(t.state,r)}}}function v$(i){let e=Object.create(null);function t(n){return e[n]||(e[n]={observers:[],handlers:[]})}for(let n of i){let r=n.spec,s=r&&r.plugin.domEventHandlers,o=r&&r.plugin.domEventObservers;if(s)for(let l in s){let a=s[l];a&&t(l).handlers.push(l0(n.value,a))}if(o)for(let l in o){let a=o[l];a&&t(l).observers.push(l0(n.value,a))}}for(let n in Ut)t(n).handlers.push(Ut[n]);for(let n in et)t(n).observers.push(et[n]);return e}var g1=[{key:"Backspace",keyCode:8,inputType:"deleteContentBackward"},{key:"Enter",keyCode:13,inputType:"insertParagraph"},{key:"Enter",keyCode:13,inputType:"insertLineBreak"},{key:"Delete",keyCode:46,inputType:"deleteContentForward"}],$$="dthko",m1=[16,17,18,20,91,92,224,225],ya=6;function Sa(i){return Math.max(0,i)*.7+8}function P$(i,e){return Math.max(Math.abs(i.clientX-e.clientX),Math.abs(i.clientY-e.clientY))}var Su=class{constructor(e,t,n,r){this.view=e,this.startEvent=t,this.style=n,this.mustSelect=r,this.scrollSpeed={x:0,y:0},this.scrolling=-1,this.lastEvent=t,this.scrollParents=D0(e.contentDOM),this.atoms=e.state.facet(So).map(o=>o(e));let s=e.contentDOM.ownerDocument;s.addEventListener("mousemove",this.move=this.move.bind(this)),s.addEventListener("mouseup",this.up=this.up.bind(this)),this.extend=t.shiftKey,this.multiple=e.state.facet(ne.allowMultipleSelections)&&C$(e,t),this.dragging=A$(e,t)&&S1(t)==1?null:!1}start(e){this.dragging===!1&&this.select(e)}move(e){if(e.buttons==0)return this.destroy();if(this.dragging||this.dragging==null&&P$(this.startEvent,e)<10)return;this.select(this.lastEvent=e);let t=0,n=0,r=0,s=0,o=this.view.win.innerWidth,l=this.view.win.innerHeight;this.scrollParents.x&&({left:r,right:o}=this.scrollParents.x.getBoundingClientRect()),this.scrollParents.y&&({top:s,bottom:l}=this.scrollParents.y.getBoundingClientRect());let a=Ku(this.view);e.clientX-a.left<=r+ya?t=-Sa(r-e.clientX):e.clientX+a.right>=o-ya&&(t=Sa(e.clientX-o)),e.clientY-a.top<=s+ya?n=-Sa(s-e.clientY):e.clientY+a.bottom>=l-ya&&(n=Sa(e.clientY-l)),this.setScrollSpeed(t,n)}up(e){this.dragging==null&&this.select(this.lastEvent),this.dragging||e.preventDefault(),this.destroy()}destroy(){this.setScrollSpeed(0,0);let e=this.view.contentDOM.ownerDocument;e.removeEventListener("mousemove",this.move),e.removeEventListener("mouseup",this.up),this.view.inputState.mouseSelection=this.view.inputState.draggedContent=null}setScrollSpeed(e,t){this.scrollSpeed={x:e,y:t},e||t?this.scrolling<0&&(this.scrolling=setInterval(()=>this.scroll(),50)):this.scrolling>-1&&(clearInterval(this.scrolling),this.scrolling=-1)}scroll(){let{x:e,y:t}=this.scrollSpeed;e&&this.scrollParents.x&&(this.scrollParents.x.scrollLeft+=e,e=0),t&&this.scrollParents.y&&(this.scrollParents.y.scrollTop+=t,t=0),(e||t)&&this.view.win.scrollBy(e,t),this.dragging===!1&&this.select(this.lastEvent)}select(e){let{view:t}=this,n=u1(this.atoms,this.style.get(e,this.extend,this.multiple));(this.mustSelect||!n.eq(t.state.selection,this.dragging===!1))&&this.view.dispatch({selection:n,userEvent:"select.pointer"}),this.mustSelect=!1}update(e){e.transactions.some(t=>t.isUserEvent("input.type"))?this.destroy():this.style.update(e)&&setTimeout(()=>this.select(this.lastEvent),20)}};function C$(i,e){let t=i.state.facet(F0);return t.length?t[0](e):_.mac?e.metaKey:e.ctrlKey}function T$(i,e){let t=i.state.facet(H0);return t.length?t[0](e):_.mac?!e.altKey:!e.ctrlKey}function A$(i,e){let{main:t}=i.state.selection;if(t.empty)return!1;let n=bo(i.root);if(!n||n.rangeCount==0)return!0;let r=n.getRangeAt(0).getClientRects();for(let s=0;s<r.length;s++){let o=r[s];if(o.left<=e.clientX&&o.right>=e.clientX&&o.top<=e.clientY&&o.bottom>=e.clientY)return!0}return!1}function M$(i,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let t=e.target,n;t!=i.contentDOM;t=t.parentNode)if(!t||t.nodeType==11||(n=fe.get(t))&&n.isWidget()&&!n.isHidden&&n.widget.ignoreEvent(e))return!1;return!0}var Ut=Object.create(null),et=Object.create(null),b1=_.ie&&_.ie_version<15||_.ios&&_.webkit_version<604;function R$(i){let e=i.dom.parentNode;if(!e)return;let t=e.appendChild(document.createElement("textarea"));t.style.cssText="position: fixed; left: -10000px; top: 10px",t.focus(),setTimeout(()=>{i.focus(),t.remove(),y1(i,t.value)},50)}function Ja(i,e,t){for(let n of i.facet(e))t=n(t,i);return t}function y1(i,e){e=Ja(i.state,qu,e);let{state:t}=i,n,r=1,s=t.toText(e),o=s.lines==t.selection.ranges.length;if(xu!=null&&t.selection.ranges.every(a=>a.empty)&&xu==s.toString()){let a=-1;n=t.changeByRange(c=>{let h=t.doc.lineAt(c.from);if(h.from==a)return{range:c};a=h.from;let f=t.toText((o?s.line(r++).text:e)+t.lineBreak);return{changes:{from:h.from,insert:f},range:v.cursor(c.from+f.length,-1)}})}else o?n=t.changeByRange(a=>{let c=s.line(r++);return{changes:{from:a.from,to:a.to,insert:c.text},range:v.cursor(a.from+c.length,-1)}}):n=t.replaceSelection(s);i.dispatch(n,{userEvent:"input.paste",scrollIntoView:!0})}et.scroll=i=>{let e=i.inputState;e.lastScrollTop=i.scrollDOM.scrollTop,e.lastScrollLeft=i.scrollDOM.scrollLeft,_.ios&&!e.touchActive&&(e.lastIOSMomentumScroll=Date.now())};et.wheel=et.mousewheel=i=>{i.inputState.lastWheelEvent=Date.now()};Ut.keydown=(i,e)=>(i.inputState.setSelectionOrigin("select"),e.keyCode==27&&i.inputState.tabFocusMode!=0&&(i.inputState.tabFocusMode=Date.now()+2e3),!1);et.touchstart=(i,e)=>{let t=i.inputState,n=e.targetTouches[0];t.touchActive=!0,t.lastTouchTime=Date.now(),n&&(t.lastTouchX=n.clientX,t.lastTouchY=n.clientY),t.setSelectionOrigin("select.pointer")};et.touchmove=i=>{i.inputState.setSelectionOrigin("select.pointer")};et.touchend=(i,e)=>{i.inputState.touchActive=!1};Ut.mousedown=(i,e)=>{if(i.observer.flush(),i.inputState.lastTouchTime>Date.now()-2e3)return!1;let t=null;for(let n of i.state.facet(K0))if(t=n(i,e),t)break;if(!t&&e.button==0&&(t=E$(i,e)),t){let n=!i.hasFocus;i.inputState.startMouseSelection(new Su(i,e,t,n)),n&&i.observer.ignore(()=>{U0(i.contentDOM);let s=i.root.activeElement;s&&!s.contains(i.contentDOM)&&s.blur()});let r=i.inputState.mouseSelection;if(r)return r.start(e),r.dragging===!1}else i.inputState.setSelectionOrigin("select.pointer");return!1};function a0(i,e,t,n){if(n==1)return v.cursor(e,t);if(n==2)return p$(i.state,e,t);{let r=i.docView.lineAt(e,t),s=i.state.doc.lineAt(r?r.posAtEnd:e),o=r?r.posAtStart:s.from,l=r?r.posAtEnd:s.to;return l<i.state.doc.length&&l==s.to&&l++,v.undirectionalRange(o,l)}}var X$=_.ie&&_.ie_version<=11,c0=null,h0=0,f0=0;function S1(i){if(!X$)return i.detail;let e=c0,t=f0;return c0=i,f0=Date.now(),h0=!e||t>Date.now()-400&&Math.abs(e.clientX-i.clientX)<2&&Math.abs(e.clientY-i.clientY)<2?(h0+1)%3:1}function E$(i,e){let t=i.posAndSideAtCoords({x:e.clientX,y:e.clientY},!1),n=S1(e),r=i.state.selection;return{update(s){s.docChanged&&(t.pos=s.changes.mapPos(t.pos),r=r.map(s.changes))},get(s,o,l){let a=i.posAndSideAtCoords({x:s.clientX,y:s.clientY},!1),c,h=a0(i,a.pos,a.assoc,n);if(t.pos!=a.pos&&!o){let f=a0(i,t.pos,t.assoc,n),u=Math.min(f.from,h.from),d=Math.max(f.to,h.to);h=u<h.from?v.range(u,d,h.assoc):v.range(d,u,h.assoc)}return o?r.replaceRange(r.main.extend(h.from,h.to,h.assoc)):l&&n==1&&r.ranges.length>1&&(c=Z$(r,a.pos))?c:l?r.addRange(h):v.create([h])}}}function Z$(i,e){for(let t=0;t<i.ranges.length;t++){let{from:n,to:r}=i.ranges[t];if(n<=e&&r>=e)return v.create(i.ranges.slice(0,t).concat(i.ranges.slice(t+1)),i.mainIndex==t?0:i.mainIndex-(i.mainIndex>t?1:0))}return null}Ut.dragstart=(i,e)=>{let{selection:{main:t}}=i.state;if(e.target.draggable){let r=i.docView.tile.nearest(e.target);if(r&&r.isWidget()){let s=r.posAtStart,o=s+r.length;(s>=t.to||o<=t.from)&&(t=v.undirectionalRange(s,o))}}let{inputState:n}=i;return n.mouseSelection&&(n.mouseSelection.dragging=!0),n.draggedContent=t,e.dataTransfer&&(e.dataTransfer.setData("Text",Ja(i.state,Gu,i.state.sliceDoc(t.from,t.to))),e.dataTransfer.effectAllowed="copyMove"),!1};Ut.dragend=i=>(i.inputState.draggedContent=null,!1);function u0(i,e,t,n){if(t=Ja(i.state,qu,t),!t)return;let r=i.posAtCoords({x:e.clientX,y:e.clientY},!1),{draggedContent:s}=i.inputState,o=n&&s&&T$(i,e)?{from:s.from,to:s.to}:null,l={from:r,insert:t},a=i.state.changes(o?[o,l]:l);i.focus(),i.dispatch({changes:a,selection:{anchor:a.mapPos(r,-1),head:a.mapPos(r,1)},userEvent:o?"move.drop":"input.drop"}),i.inputState.draggedContent=null}Ut.drop=(i,e)=>{if(!e.dataTransfer)return!1;if(i.state.readOnly)return!0;let t=e.dataTransfer.files;if(t&&t.length){let n=Array(t.length),r=0,s=()=>{++r==t.length&&u0(i,e,n.filter(o=>o!=null).join(i.state.lineBreak),!1)};for(let o=0;o<t.length;o++){let l=new FileReader;l.onerror=s,l.onload=()=>{/[\x00-\x08\x0e-\x1f]{2}/.test(l.result)||(n[o]=l.result),s()},l.readAsText(t[o])}return!0}else{let n=e.dataTransfer.getData("Text");if(n)return u0(i,e,n,!0),!0}return!1};Ut.paste=(i,e)=>{if(i.state.readOnly)return!0;i.observer.flush();let t=b1?null:e.clipboardData;return t?(y1(i,t.getData("text/plain")||t.getData("text/uri-list")),!0):(R$(i),!1)};function _$(i,e){let t=i.dom.parentNode;if(!t)return;let n=t.appendChild(document.createElement("textarea"));n.style.cssText="position: fixed; left: -10000px; top: 10px",n.value=e,n.focus(),n.selectionEnd=e.length,n.selectionStart=0,setTimeout(()=>{n.remove(),i.focus()},50)}function L$(i){let e=[],t=[],n=!1;for(let r of i.selection.ranges)r.empty||(e.push(i.sliceDoc(r.from,r.to)),t.push(r));if(!e.length){let r=-1;for(let{from:s}of i.selection.ranges){let o=i.doc.lineAt(s);o.number>r&&(e.push(o.text),t.push({from:o.from,to:Math.min(i.doc.length,o.to+1)})),r=o.number}n=!0}return{text:Ja(i,Gu,e.join(i.lineBreak)),ranges:t,linewise:n}}var xu=null;Ut.copy=Ut.cut=(i,e)=>{if(!oo(i.contentDOM,i.observer.selectionRange))return!1;let{text:t,ranges:n,linewise:r}=L$(i.state);if(!t&&!r)return!1;xu=r?t:null,e.type=="cut"&&!i.state.readOnly&&i.dispatch({changes:n,scrollIntoView:!0,userEvent:"delete.cut"});let s=b1?null:e.clipboardData;return s?(s.clearData(),s.setData("text/plain",t),!0):(_$(i,t),!1)};var x1=we.define();function w1(i,e){let t=[];for(let n of i.facet(t1)){let r=n(i,e);r&&t.push(r)}return t.length?i.update({effects:t,annotations:x1.of(!0)}):null}function k1(i){setTimeout(()=>{let e=i.hasFocus;if(e!=i.inputState.notifiedFocused){let t=w1(i.state,e);t?i.dispatch(t):i.update([])}},10)}et.focus=i=>{i.inputState.lastFocusTime=Date.now(),!i.scrollDOM.scrollTop&&(i.inputState.lastScrollTop||i.inputState.lastScrollLeft)&&(i.scrollDOM.scrollTop=i.inputState.lastScrollTop,i.scrollDOM.scrollLeft=i.inputState.lastScrollLeft),k1(i)};et.blur=i=>{i.observer.clearSelectionRange(),k1(i)};et.compositionstart=et.compositionupdate=i=>{if(!i.observer.editContext&&(i.inputState.compositionFirstChange==null&&(i.inputState.compositionFirstChange=!0),i.inputState.composing<0)){let{main:e}=i.state.selection;!e.empty&&i.lineBlockAt(e.from).from!=i.lineBlockAt(e.to).from&&i.dispatch({changes:i.state.selection.ranges.filter(t=>!t.empty).map(t=>({from:t.from,to:t.to})),userEvent:"input"}),i.inputState.composing=0}};et.compositionend=i=>{i.observer.editContext||(i.inputState.composing=-1,i.inputState.compositionEndedAt=Date.now(),i.inputState.compositionPendingKey=!0,i.inputState.compositionPendingChange=i.observer.pendingRecords().length>0,i.inputState.compositionFirstChange=null,_.chrome&&_.android?i.observer.flushSoon():i.inputState.compositionPendingChange?Promise.resolve().then(()=>i.observer.flush()):setTimeout(()=>{i.inputState.composing<0&&i.docView.hasComposition&&i.update([])},50))};et.contextmenu=i=>{i.inputState.lastContextMenu=Date.now()};Ut.beforeinput=(i,e)=>{var t,n;if((e.inputType=="insertText"||e.inputType=="insertCompositionText")&&(i.inputState.insertingText=e.data,i.inputState.insertingTextAt=Date.now()),e.inputType=="insertReplacementText"&&i.observer.editContext){let s=(t=e.dataTransfer)===null||t===void 0?void 0:t.getData("text/plain"),o=e.getTargetRanges();if(s&&o.length){let l=o[0],a=i.posAtDOM(l.startContainer,l.startOffset),c=i.posAtDOM(l.endContainer,l.endOffset);return Ju(i,{from:a,to:c,insert:i.state.toText(s)},null),!0}}let r;if(_.chrome&&_.android&&(r=g1.find(s=>s.inputType==e.inputType))&&(i.observer.delayAndroidKey(r.key,r.keyCode),r.key=="Backspace"||r.key=="Delete")){let s=((n=window.visualViewport)===null||n===void 0?void 0:n.height)||0;setTimeout(()=>{var o;(((o=window.visualViewport)===null||o===void 0?void 0:o.height)||0)>s+10&&i.hasFocus&&(i.contentDOM.blur(),i.focus())},100)}return _.ios&&e.inputType=="deleteContentForward"&&i.observer.flushSoon(),_.safari&&e.inputType=="insertText"&&i.inputState.composing>=0&&setTimeout(()=>et.compositionend(i,e),20),!1};var d0=new Set;function D$(i){d0.has(i)||(d0.add(i),i.addEventListener("copy",()=>{}),i.addEventListener("cut",()=>{}))}var p0=["pre-wrap","normal","pre-line","break-spaces"],es=!1;function O0(){es=!1}var wu=class{constructor(e){this.lineWrapping=e,this.doc=q.empty,this.heightSamples={},this.lineHeight=14,this.charWidth=7,this.textHeight=14,this.lineLength=30}heightForGap(e,t){let n=this.doc.lineAt(t).number-this.doc.lineAt(e).number+1;return this.lineWrapping&&(n+=Math.max(0,Math.ceil((t-e-n*this.lineLength*.5)/this.lineLength))),this.lineHeight*n}heightForLine(e){return this.lineWrapping?(1+Math.max(0,Math.ceil((e-this.lineLength)/Math.max(1,this.lineLength-5))))*this.lineHeight:this.lineHeight}setDoc(e){return this.doc=e,this}mustRefreshForWrapping(e){return p0.indexOf(e)>-1!=this.lineWrapping}mustRefreshForHeights(e){let t=!1;for(let n=0;n<e.length;n++){let r=e[n];r<0?n++:this.heightSamples[Math.floor(r*10)]||(t=!0,this.heightSamples[Math.floor(r*10)]=!0)}return t}refresh(e,t,n,r,s,o){let l=p0.indexOf(e)>-1,a=Math.abs(t-this.lineHeight)>.3||this.lineWrapping!=l;if(this.lineWrapping=l,this.lineHeight=t,this.charWidth=n,this.textHeight=r,this.lineLength=s,a){this.heightSamples={};for(let c=0;c<o.length;c++){let h=o[c];h<0?c++:this.heightSamples[Math.floor(h*10)]=!0}}return a}},ku=class{constructor(e,t){this.from=e,this.heights=t,this.index=0}get more(){return this.index<this.heights.length}},jt=class i{constructor(e,t,n,r,s){this.from=e,this.length=t,this.top=n,this.height=r,this._content=s}get type(){return typeof this._content=="number"?Le.Text:Array.isArray(this._content)?this._content:this._content.type}get to(){return this.from+this.length}get bottom(){return this.top+this.height}get widget(){return this._content instanceof In?this._content.widget:null}get widgetLineBreaks(){return typeof this._content=="number"?this._content:0}join(e){let t=(Array.isArray(this._content)?this._content:[this]).concat(Array.isArray(e._content)?e._content:[e]);return new i(this.from,this.length+e.length,this.top,this.height+e.height,t)}},he=(function(i){return i[i.ByPos=0]="ByPos",i[i.ByHeight=1]="ByHeight",i[i.ByPosNoHeight=2]="ByPosNoHeight",i})(he||(he={})),Ca=.001,bt=class i{constructor(e,t,n=2){this.length=e,this.height=t,this.flags=n}get outdated(){return(this.flags&2)>0}set outdated(e){this.flags=(e?2:0)|this.flags&-3}setHeight(e){this.height!=e&&(Math.abs(this.height-e)>Ca&&(es=!0),this.height=e)}replace(e,t,n){return i.of(n)}decomposeLeft(e,t){t.push(this)}decomposeRight(e,t){t.push(this)}applyChanges(e,t,n,r){let s=this,o=n.doc;for(let l=r.length-1;l>=0;l--){let{fromA:a,toA:c,fromB:h,toB:f}=r[l],u=s.lineAt(a,he.ByPosNoHeight,n.setDoc(t),0,0),d=u.to>=c?u:s.lineAt(c,he.ByPosNoHeight,n,0,0);for(f+=d.to-c,c=d.to;l>0&&u.from<=r[l-1].toA;)a=r[l-1].fromA,h=r[l-1].fromB,l--,a<u.from&&(u=s.lineAt(a,he.ByPosNoHeight,n,0,0));h+=u.from-a,a=u.from;let p=vu.build(n.setDoc(o),e,h,f);s=Ba(s,s.replace(a,c,p))}return s.updateHeight(n,0)}static empty(){return new At(0,0,0)}static of(e){if(e.length==1)return e[0];let t=0,n=e.length,r=0,s=0;for(;;)if(t==n)if(r>s*2){let l=e[t-1];l.break?e.splice(--t,1,l.left,null,l.right):e.splice(--t,1,l.left,l.right),n+=1+l.break,r-=l.size}else if(s>r*2){let l=e[n];l.break?e.splice(n,1,l.left,null,l.right):e.splice(n,1,l.left,l.right),n+=2+l.break,s-=l.size}else break;else if(r<s){let l=e[t++];l&&(r+=l.size)}else{let l=e[--n];l&&(s+=l.size)}let o=!1;return e[t-1]==null?(o=!0,t--):e[t]==null&&(o=!0,n++),new Qu(i.of(e.slice(0,t)),o,i.of(e.slice(n)))}};function Ba(i,e){return i==e?i:(i.constructor!=e.constructor&&(es=!0),e)}bt.prototype.size=1;var j$=L.replace({}),Va=class extends bt{constructor(e,t,n){super(e,t),this.deco=n,this.spaceAbove=0}mainBlock(e,t){return new jt(t,this.length,e+this.spaceAbove,this.height-this.spaceAbove,this.deco||0)}blockAt(e,t,n,r){return this.spaceAbove&&e<n+this.spaceAbove?new jt(r,0,n,this.spaceAbove,j$):this.mainBlock(n,r)}lineAt(e,t,n,r,s){let o=this.mainBlock(r,s);return this.spaceAbove?this.blockAt(0,n,r,s).join(o):o}forEachLine(e,t,n,r,s,o){e<=s+this.length&&t>=s&&o(this.lineAt(0,he.ByPos,n,r,s))}setMeasuredHeight(e){let t=e.heights[e.index++];t<0?(this.spaceAbove=-t,t=e.heights[e.index++]):this.spaceAbove=0,this.setHeight(t)}updateHeight(e,t=0,n=!1,r){return r&&r.from<=t&&r.more&&this.setMeasuredHeight(r),this.outdated=!1,this}toString(){return`block(${this.length})`}},At=class i extends Va{constructor(e,t,n){super(e,t,null),this.collapsed=0,this.widgetHeight=0,this.breaks=0,this.spaceAbove=n}mainBlock(e,t){return new jt(t,this.length,e+this.spaceAbove,this.height-this.spaceAbove,this.breaks)}replace(e,t,n){let r=n[0];return n.length==1&&(r instanceof i||r instanceof Ui&&r.flags&4)&&Math.abs(this.length-r.length)<10?(r instanceof Ui?r=new i(r.length,this.height,this.spaceAbove):r.height=this.height,this.outdated||(r.outdated=!1),r):bt.of(n)}updateHeight(e,t=0,n=!1,r){return r&&r.from<=t&&r.more?this.setMeasuredHeight(r):(n||this.outdated)&&(this.spaceAbove=0,this.setHeight(Math.max(this.widgetHeight,e.heightForLine(this.length-this.collapsed))+this.breaks*e.lineHeight)),this.outdated=!1,this}toString(){return`line(${this.length}${this.collapsed?-this.collapsed:""}${this.widgetHeight?":"+this.widgetHeight:""})`}},Ui=class i extends bt{constructor(e){super(e,0)}heightMetrics(e,t){let n=e.doc.lineAt(t).number,r=e.doc.lineAt(t+this.length).number,s=r-n+1,o,l=0;if(e.lineWrapping){let a=Math.min(this.height,e.lineHeight*s);o=a/s,this.length>s+1&&(l=(this.height-a)/(this.length-s-1))}else o=this.height/s;return{firstLine:n,lastLine:r,perLine:o,perChar:l}}blockAt(e,t,n,r){let{firstLine:s,lastLine:o,perLine:l,perChar:a}=this.heightMetrics(t,r);if(t.lineWrapping){let c=r+(e<t.lineHeight?0:Math.round(Math.max(0,Math.min(1,(e-n)/this.height))*this.length)),h=t.doc.lineAt(c),f=l+h.length*a,u=Math.max(n,e-f/2);return new jt(h.from,h.length,u,f,0)}else{let c=Math.max(0,Math.min(o-s,Math.floor((e-n)/l))),{from:h,length:f}=t.doc.line(s+c);return new jt(h,f,n+l*c,l,0)}}lineAt(e,t,n,r,s){if(t==he.ByHeight)return this.blockAt(e,n,r,s);if(t==he.ByPosNoHeight){let{from:d,to:p}=n.doc.lineAt(e);return new jt(d,p-d,0,0,0)}let{firstLine:o,perLine:l,perChar:a}=this.heightMetrics(n,s),c=n.doc.lineAt(e),h=l+c.length*a,f=c.number-o,u=r+l*f+a*(c.from-s-f);return new jt(c.from,c.length,Math.max(r,Math.min(u,r+this.height-h)),h,0)}forEachLine(e,t,n,r,s,o){e=Math.max(e,s),t=Math.min(t,s+this.length);let{firstLine:l,perLine:a,perChar:c}=this.heightMetrics(n,s);for(let h=e,f=r;h<=t;){let u=n.doc.lineAt(h);if(h==e){let p=u.number-l;f+=a*p+c*(e-s-p)}let d=a+c*u.length;o(new jt(u.from,u.length,f,d,0)),f+=d,h=u.to+1}}replace(e,t,n){let r=this.length-t;if(r>0){let s=n[n.length-1];s instanceof i?n[n.length-1]=new i(s.length+r):n.push(null,new i(r-1))}if(e>0){let s=n[0];s instanceof i?n[0]=new i(e+s.length):n.unshift(new i(e-1),null)}return bt.of(n)}decomposeLeft(e,t){t.push(new i(e-1),null)}decomposeRight(e,t){t.push(null,new i(this.length-e-1))}updateHeight(e,t=0,n=!1,r){let s=t+this.length;if(r&&r.from<=t+this.length&&r.more){let o=[],l=Math.max(t,r.from),a=-1;for(r.from>t&&o.push(new i(r.from-t-1).updateHeight(e,t));l<=s&&r.more;){let h=e.doc.lineAt(l).length;o.length&&o.push(null);let f=r.heights[r.index++],u=0;f<0&&(u=-f,f=r.heights[r.index++]),a==-1?a=f:Math.abs(f-a)>=Ca&&(a=-2);let d=new At(h,f,u);d.outdated=!1,o.push(d),l+=h+1}l<=s&&o.push(null,new i(s-l).updateHeight(e,l));let c=bt.of(o);return(a<0||Math.abs(c.height-this.height)>=Ca||Math.abs(a-this.heightMetrics(e,t).perLine)>=Ca)&&(es=!0),Ba(this,c)}else(n||this.outdated)&&(this.setHeight(e.heightForGap(t,t+this.length)),this.outdated=!1);return this}toString(){return`gap(${this.length})`}},Qu=class extends bt{constructor(e,t,n){super(e.length+(t?1:0)+n.length,e.height+n.height,(t?1:0)|(e.outdated||n.outdated?2:0)),this.left=e,this.right=n,this.size=e.size+n.size}get break(){return this.flags&1}blockAt(e,t,n,r){let s=n+this.left.height;return e<s?this.left.blockAt(e,t,n,r):this.right.blockAt(e,t,s,r+this.left.length+this.break)}lineAt(e,t,n,r,s){let o=r+this.left.height,l=s+this.left.length+this.break,a=t==he.ByHeight?e<o:e<l,c=a?this.left.lineAt(e,t,n,r,s):this.right.lineAt(e,t,n,o,l);if(this.break||(a?c.to<l:c.from>l))return c;let h=t==he.ByPosNoHeight?he.ByPosNoHeight:he.ByPos;return a?c.join(this.right.lineAt(l,h,n,o,l)):this.left.lineAt(l,h,n,r,s).join(c)}forEachLine(e,t,n,r,s,o){let l=r+this.left.height,a=s+this.left.length+this.break;if(this.break)e<a&&this.left.forEachLine(e,t,n,r,s,o),t>=a&&this.right.forEachLine(e,t,n,l,a,o);else{let c=this.lineAt(a,he.ByPos,n,r,s);e<c.from&&this.left.forEachLine(e,Math.min(t,c.from-1),n,r,s,o),c.to>=e&&c.from<=t&&o(c),t>c.to&&this.right.forEachLine(Math.max(e,c.to+1),t,n,l,a,o)}}replace(e,t,n){let r=this.left.length+this.break;if(t<r)return this.balanced(this.left.replace(e,t,n),this.right);if(e>this.left.length)return this.balanced(this.left,this.right.replace(e-r,t-r,n));let s=[];e>0&&this.decomposeLeft(e,s);let o=s.length;for(let l of n)s.push(l);if(e>0&&g0(s,o-1),t<this.length){let l=s.length;this.decomposeRight(t,s),g0(s,l)}return bt.of(s)}decomposeLeft(e,t){let n=this.left.length;if(e<=n)return this.left.decomposeLeft(e,t);t.push(this.left),this.break&&(n++,e>=n&&t.push(null)),e>n&&this.right.decomposeLeft(e-n,t)}decomposeRight(e,t){let n=this.left.length,r=n+this.break;if(e>=r)return this.right.decomposeRight(e-r,t);e<n&&this.left.decomposeRight(e,t),this.break&&e<r&&t.push(null),t.push(this.right)}balanced(e,t){return e.size>2*t.size||t.size>2*e.size?bt.of(this.break?[e,null,t]:[e,t]):(this.left=Ba(this.left,e),this.right=Ba(this.right,t),this.setHeight(e.height+t.height),this.outdated=e.outdated||t.outdated,this.size=e.size+t.size,this.length=e.length+this.break+t.length,this)}updateHeight(e,t=0,n=!1,r){let{left:s,right:o}=this,l=t+s.length+this.break,a=null;return r&&r.from<=t+s.length&&r.more?a=s=s.updateHeight(e,t,n,r):s.updateHeight(e,t,n),r&&r.from<=l+o.length&&r.more?a=o=o.updateHeight(e,l,n,r):o.updateHeight(e,l,n),a?this.balanced(s,o):(this.height=this.left.height+this.right.height,this.outdated=!1,this)}toString(){return this.left+(this.break?" ":"-")+this.right}};function g0(i,e){let t,n;i[e]==null&&(t=i[e-1])instanceof Ui&&(n=i[e+1])instanceof Ui&&i.splice(e-1,3,new Ui(t.length+1+n.length))}var I$=5,vu=class i{constructor(e,t){this.pos=e,this.oracle=t,this.nodes=[],this.lineStart=-1,this.lineEnd=-1,this.covering=null,this.writtenTo=e}get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]==this.covering}span(e,t){if(this.lineStart>-1){let n=Math.min(t,this.lineEnd),r=this.nodes[this.nodes.length-1];r instanceof At?r.length+=n-this.pos:(n>this.pos||!this.isCovered)&&this.nodes.push(new At(n-this.pos,-1,0)),this.writtenTo=n,t>n&&(this.nodes.push(null),this.writtenTo++,this.lineStart=-1)}this.pos=t}point(e,t,n){if(e<t||n.heightRelevant){let r=n.widget?n.widget.estimatedHeight:0,s=n.widget?n.widget.lineBreaks:0;r<0&&(r=this.oracle.lineHeight);let o=t-e;n.block?this.addBlock(new Va(o,r,n)):(o||s||r>=I$)&&this.addLineDeco(r,s,o)}else t>e&&this.span(e,t);this.lineEnd>-1&&this.lineEnd<this.pos&&(this.lineEnd=this.oracle.doc.lineAt(this.pos).to)}enterLine(){if(this.lineStart>-1)return;let{from:e,to:t}=this.oracle.doc.lineAt(this.pos);this.lineStart=e,this.lineEnd=t,this.writtenTo<e&&((this.writtenTo<e-1||this.nodes[this.nodes.length-1]==null)&&this.nodes.push(this.blankContent(this.writtenTo,e-1)),this.nodes.push(null)),this.pos>e&&this.nodes.push(new At(this.pos-e,-1,0)),this.writtenTo=this.pos}blankContent(e,t){let n=new Ui(t-e);return this.oracle.doc.lineAt(e).to==t&&(n.flags|=4),n}ensureLine(){this.enterLine();let e=this.nodes.length?this.nodes[this.nodes.length-1]:null;if(e instanceof At)return e;let t=new At(0,-1,0);return this.nodes.push(t),t}addBlock(e){this.enterLine();let t=e.deco;t&&t.startSide>0&&!this.isCovered&&this.ensureLine(),this.nodes.push(e),this.writtenTo=this.pos=this.pos+e.length,t&&t.endSide>0&&(this.covering=e)}addLineDeco(e,t,n){let r=this.ensureLine();r.length+=n,r.collapsed+=n,r.widgetHeight=Math.max(r.widgetHeight,e),r.breaks+=t,this.writtenTo=this.pos=this.pos+n}finish(e){let t=this.nodes.length==0?null:this.nodes[this.nodes.length-1];this.lineStart>-1&&!(t instanceof At)&&!this.isCovered?this.nodes.push(new At(0,-1,0)):(this.writtenTo<this.pos||t==null)&&this.nodes.push(this.blankContent(this.writtenTo,this.pos));let n=e;for(let r of this.nodes)r instanceof At&&r.updateHeight(this.oracle,n),n+=r?r.length:1;return this.nodes}static build(e,t,n,r){let s=new i(n,e);return G.spans(t,n,r,s,0),s.finish(n)}};function U$(i,e,t){let n=new $u;return G.compare(i,e,t,n,0),n.changes}var $u=class{constructor(){this.changes=[]}compareRange(){}comparePoint(e,t,n,r){(e<t||n&&n.heightRelevant||r&&r.heightRelevant)&&qr(e,t,this.changes,5)}};function B$(i,e){let t=i.getBoundingClientRect(),n=i.ownerDocument,r=n.defaultView||window,s=Math.max(0,t.left),o=Math.min(r.innerWidth,t.right),l=Math.max(0,t.top),a=Math.min(r.innerHeight,t.bottom);for(let c=i.parentNode;c&&c!=n.body;)if(c.nodeType==1){let h=c,f=window.getComputedStyle(h);if((h.scrollHeight>h.clientHeight||h.scrollWidth>h.clientWidth)&&f.overflow!="visible"){let u=h.getBoundingClientRect();s=Math.max(s,u.left),o=Math.min(o,u.right),l=Math.max(l,u.top),a=Math.min(c==i.parentNode?r.innerHeight:a,u.bottom)}c=f.position=="absolute"||f.position=="fixed"?h.offsetParent:h.parentNode}else if(c.nodeType==11)c=c.host;else break;return{left:s-t.left,right:Math.max(s,o)-t.left,top:l-(t.top+e),bottom:Math.max(l,a)-(t.top+e)}}function V$(i){let e=i.getBoundingClientRect(),t=i.ownerDocument.defaultView||window;return e.left<t.innerWidth&&e.right>0&&e.top<t.innerHeight&&e.bottom>0}function Y$(i,e){let t=i.getBoundingClientRect();return{left:0,right:t.right-t.left,top:e,bottom:t.bottom-(t.top+e)}}var uo=class{constructor(e,t,n,r){this.from=e,this.to=t,this.size=n,this.displaySize=r}static same(e,t){if(e.length!=t.length)return!1;for(let n=0;n<e.length;n++){let r=e[n],s=t[n];if(r.from!=s.from||r.to!=s.to||r.size!=s.size)return!1}return!0}draw(e,t){return L.replace({widget:new Pu(this.displaySize*(t?e.scaleY:e.scaleX),t)}).range(this.from,this.to)}},Pu=class extends De{constructor(e,t){super(),this.size=e,this.vertical=t}eq(e){return e.size==this.size&&e.vertical==this.vertical}toDOM(){let e=document.createElement("div");return this.vertical?e.style.height=this.size+"px":(e.style.width=this.size+"px",e.style.height="2px",e.style.display="inline-block"),e}get estimatedHeight(){return this.vertical?this.size:-1}},Ya=class{constructor(e,t){this.view=e,this.state=t,this.pixelViewport={left:0,right:window.innerWidth,top:0,bottom:0},this.inView=!0,this.paddingTop=0,this.paddingBottom=0,this.contentDOMWidth=0,this.contentDOMHeight=0,this.editorHeight=0,this.editorWidth=0,this.scaleX=1,this.scaleY=1,this.scrollOffset=0,this.scrolledToBottom=!1,this.scrollAnchorPos=0,this.scrollAnchorHeight=-1,this.scaler=m0,this.scrollTarget=null,this.printing=!1,this.mustMeasureContent=!0,this.defaultTextDirection=re.LTR,this.visibleRanges=[],this.mustEnforceCursorAssoc=!1;let n=t.facet(Fu).some(r=>typeof r!="function"&&r.class=="cm-lineWrapping");this.heightOracle=new wu(n),this.stateDeco=b0(t),this.heightMap=bt.empty().applyChanges(this.stateDeco,q.empty,this.heightOracle.setDoc(t.doc),[new It(0,0,0,t.doc.length)]);for(let r=0;r<2&&(this.viewport=this.getViewport(0,null),!!this.updateForViewport());r++);this.updateViewportLines(),this.lineGaps=this.ensureLineGaps([]),this.lineGapDeco=L.set(this.lineGaps.map(r=>r.draw(this,!1))),this.scrollParent=e.scrollDOM,this.computeVisibleRanges()}updateForViewport(){let e=[this.viewport],{main:t}=this.state.selection;for(let n=0;n<=1;n++){let r=n?t.head:t.anchor;if(!e.some(({from:s,to:o})=>r>=s&&r<=o)){let{from:s,to:o}=this.lineBlockAt(r);e.push(new zr(s,o))}}return this.viewports=e.sort((n,r)=>n.from-r.from),this.updateScaler()}updateScaler(){let e=this.scaler;return this.scaler=this.heightMap.height<=7e6?m0:new Cu(this.heightOracle,this.heightMap,this.viewports),e.eq(this.scaler)?0:2}updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine(this.viewport.from,this.viewport.to,this.heightOracle.setDoc(this.state.doc),0,0,e=>{this.viewportLines.push(ro(e,this.scaler))})}update(e,t=null){this.state=e.state;let n=this.stateDeco;this.stateDeco=b0(this.state);let r=e.changedRanges,s=It.extendWithRanges(r,U$(n,this.stateDeco,e?e.changes:He.empty(this.state.doc.length))),o=this.heightMap.height,l=this.scrolledToBottom?null:this.scrollAnchorAt(this.scrollOffset);O0(),this.heightMap=this.heightMap.applyChanges(this.stateDeco,e.startState.doc,this.heightOracle.setDoc(this.state.doc),s),(this.heightMap.height!=o||es)&&(e.flags|=2),l?(this.scrollAnchorPos=e.changes.mapPos(l.from,-1),this.scrollAnchorHeight=l.top):(this.scrollAnchorPos=-1,this.scrollAnchorHeight=o);let a=s.length?this.mapViewport(this.viewport,e.changes):this.viewport;(t&&(t.range.head<a.from||t.range.head>a.to)||!this.viewportIsAppropriate(a))&&(a=this.getViewport(0,t));let c=a.from!=this.viewport.from||a.to!=this.viewport.to;this.viewport=a,e.flags|=this.updateForViewport(),(c||!e.changes.empty||e.flags&2)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps,e.changes))),e.flags|=this.computeVisibleRanges(e.changes),t&&(this.scrollTarget=t),!this.mustEnforceCursorAssoc&&(e.selectionSet||e.focusChanged)&&e.view.lineWrapping&&e.state.selection.main.empty&&e.state.selection.main.assoc&&!e.state.facet(n1)&&(this.mustEnforceCursorAssoc=!0)}measure(){let{view:e}=this,t=e.contentDOM,n=window.getComputedStyle(t),r=this.heightOracle,s=n.whiteSpace;this.defaultTextDirection=n.direction=="rtl"?re.RTL:re.LTR;let o=this.heightOracle.mustRefreshForWrapping(s)||this.mustMeasureContent==="refresh",l=t.getBoundingClientRect(),a=o||this.mustMeasureContent||this.contentDOMHeight!=l.height;this.contentDOMHeight=l.height,this.mustMeasureContent=!1;let c=0,h=0;if(l.width&&l.height){let{scaleX:C,scaleY:T}=L0(t,l);(C>.005&&Math.abs(this.scaleX-C)>.005||T>.005&&Math.abs(this.scaleY-T)>.005)&&(this.scaleX=C,this.scaleY=T,c|=16,o=a=!0)}let f=(parseInt(n.paddingTop)||0)*this.scaleY,u=(parseInt(n.paddingBottom)||0)*this.scaleY;(this.paddingTop!=f||this.paddingBottom!=u)&&(this.paddingTop=f,this.paddingBottom=u,c|=18),this.editorWidth!=e.scrollDOM.clientWidth&&(r.lineWrapping&&(a=!0),this.editorWidth=e.scrollDOM.clientWidth,c|=16);let d=D0(this.view.contentDOM,!1).y;d!=this.scrollParent&&(this.scrollParent=d,this.scrollAnchorHeight=-1,this.scrollOffset=0);let p=this.getScrollOffset();this.scrollOffset!=p&&(this.scrollAnchorHeight=-1,this.scrollOffset=p),this.scrolledToBottom=B0(this.scrollParent||e.win);let O=(this.printing?Y$:B$)(t,this.paddingTop),g=O.top-this.pixelViewport.top,b=O.bottom-this.pixelViewport.bottom;this.pixelViewport=O;let w=this.pixelViewport.bottom>this.pixelViewport.top&&this.pixelViewport.right>this.pixelViewport.left;if(w!=this.inView&&(this.inView=w,w&&(a=!0)),!this.inView&&!this.scrollTarget&&!V$(e.dom))return 0;let x=l.width;if((this.contentDOMWidth!=x||this.editorHeight!=e.scrollDOM.clientHeight)&&(this.contentDOMWidth=l.width,this.editorHeight=e.scrollDOM.clientHeight,c|=16),a){let C=e.docView.measureVisibleLineHeights(this.viewport);if(r.mustRefreshForHeights(C)&&(o=!0),o||r.lineWrapping&&Math.abs(x-this.contentDOMWidth)>r.charWidth){let{lineHeight:T,charWidth:y,textHeight:Q}=e.docView.measureTextSize();o=T>0&&r.refresh(s,T,y,Q,Math.max(5,x/y),C),o&&(e.docView.minWidth=0,c|=16)}g>0&&b>0?h=Math.max(g,b):g<0&&b<0&&(h=Math.min(g,b)),O0();for(let T of this.viewports){let y=T.from==this.viewport.from?C:e.docView.measureVisibleLineHeights(T);this.heightMap=(o?bt.empty().applyChanges(this.stateDeco,q.empty,this.heightOracle,[new It(0,0,0,e.state.doc.length)]):this.heightMap).updateHeight(r,0,o,new ku(T.from,y))}es&&(c|=2)}let X=!this.viewportIsAppropriate(this.viewport,h)||this.scrollTarget&&(this.scrollTarget.range.head<this.viewport.from||this.scrollTarget.range.head>this.viewport.to);return X&&(c&2&&(c|=this.updateScaler()),this.viewport=this.getViewport(h,this.scrollTarget),c|=this.updateForViewport()),(c&2||X)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(o?[]:this.lineGaps,e)),c|=this.computeVisibleRanges(),this.mustEnforceCursorAssoc&&(this.mustEnforceCursorAssoc=!1,e.docView.enforceCursorAssoc()),c}get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bottom)}getViewport(e,t){let n=.5-Math.max(-.5,Math.min(.5,e/1e3/2)),r=this.heightMap,s=this.heightOracle,{visibleTop:o,visibleBottom:l}=this,a=new zr(r.lineAt(o-n*1e3,he.ByHeight,s,0,0).from,r.lineAt(l+(1-n)*1e3,he.ByHeight,s,0,0).to);if(t){let{head:c}=t.range;if(c<a.from||c>a.to){let h=Math.min(this.editorHeight,this.pixelViewport.bottom-this.pixelViewport.top),f=r.lineAt(c,he.ByPos,s,0,0),u;t.y=="center"?u=(f.top+f.bottom)/2-h/2:t.y=="start"||t.y=="nearest"&&c<a.from?u=f.top:u=f.bottom-h,a=new zr(r.lineAt(u-1e3/2,he.ByHeight,s,0,0).from,r.lineAt(u+h+1e3/2,he.ByHeight,s,0,0).to)}}return a}mapViewport(e,t){let n=t.mapPos(e.from,-1),r=t.mapPos(e.to,1);return new zr(this.heightMap.lineAt(n,he.ByPos,this.heightOracle,0,0).from,this.heightMap.lineAt(r,he.ByPos,this.heightOracle,0,0).to)}viewportIsAppropriate({from:e,to:t},n=0){if(!this.inView)return!0;let{top:r}=this.heightMap.lineAt(e,he.ByPos,this.heightOracle,0,0),{bottom:s}=this.heightMap.lineAt(t,he.ByPos,this.heightOracle,0,0),{visibleTop:o,visibleBottom:l}=this;return(e==0||r<=o-Math.max(10,Math.min(-n,250)))&&(t==this.state.doc.length||s>=l+Math.max(10,Math.min(n,250)))&&r>o-2*1e3&&s<l+2*1e3}mapLineGaps(e,t){if(!e.length||t.empty)return e;let n=[];for(let r of e)t.touchesRange(r.from,r.to)||n.push(new uo(t.mapPos(r.from),t.mapPos(r.to),r.size,r.displaySize));return n}ensureLineGaps(e,t){let n=this.heightOracle.lineWrapping,r=n?1e4:2e3,s=r>>1,o=r<<1;if(this.defaultTextDirection!=re.LTR&&!n)return[];let l=[],a=(h,f,u,d)=>{if(f-h<s)return;let p=this.state.selection.main,O=[p.from];p.empty||O.push(p.to);for(let b of O)if(b>h&&b<f){a(h,b-10,u,d),a(b+10,f,u,d);return}let g=W$(e,b=>b.from>=u.from&&b.to<=u.to&&Math.abs(b.from-h)<s&&Math.abs(b.to-f)<s&&!O.some(w=>b.from<w&&b.to>w));if(!g){if(f<u.to&&t&&n&&t.visibleRanges.some(x=>x.from<=f&&x.to>=f)){let x=t.moveToLineBoundary(v.cursor(f),!1,!0).head;x>h&&(f=x)}let b=this.gapSize(u,h,f,d),w=n||b<2e6?b:2e6;g=new uo(h,f,b,w)}l.push(g)},c=h=>{if(h.length<o||h.type!=Le.Text)return;let f=z$(h.from,h.to,this.stateDeco);if(f.total<o)return;let u=this.scrollTarget?this.scrollTarget.range.head:null,d,p;if(n){let O=r/this.heightOracle.lineLength*this.heightOracle.lineHeight,g,b;if(u!=null){let w=wa(f,u),x=((this.visibleBottom-this.visibleTop)/2+O)/h.height;g=w-x,b=w+x}else g=(this.visibleTop-h.top-O)/h.height,b=(this.visibleBottom-h.top+O)/h.height;d=xa(f,g),p=xa(f,b)}else{let O=f.total*this.heightOracle.charWidth,g=r*this.heightOracle.charWidth,b=0;if(O>2e6)for(let T of e)T.from>=h.from&&T.from<h.to&&T.size!=T.displaySize&&T.from*this.heightOracle.charWidth+b<this.pixelViewport.left&&(b=T.size-T.displaySize);let w=this.pixelViewport.left+b,x=this.pixelViewport.right+b,X,C;if(u!=null){let T=wa(f,u),y=((x-w)/2+g)/O;X=T-y,C=T+y}else X=(w-g)/O,C=(x+g)/O;d=xa(f,X),p=xa(f,C)}d>h.from&&a(h.from,d,h,f),p<h.to&&a(p,h.to,h,f)};for(let h of this.viewportLines)Array.isArray(h.type)?h.type.forEach(c):c(h);return l}gapSize(e,t,n,r){let s=wa(r,n)-wa(r,t);return this.heightOracle.lineWrapping?e.height*s:r.total*this.heightOracle.charWidth*s}updateLineGaps(e){uo.same(e,this.lineGaps)||(this.lineGaps=e,this.lineGapDeco=L.set(e.map(t=>t.draw(this,this.heightOracle.lineWrapping))))}computeVisibleRanges(e){let t=this.stateDeco;this.lineGaps.length&&(t=t.concat(this.lineGapDeco));let n=[];G.spans(t,this.viewport.from,this.viewport.to,{span(s,o){n.push({from:s,to:o})},point(){}},20);let r=0;if(n.length!=this.visibleRanges.length)r=12;else for(let s=0;s<n.length&&!(r&8);s++){let o=this.visibleRanges[s],l=n[s];(o.from!=l.from||o.to!=l.to)&&(r|=4,e&&e.mapPos(o.from,-1)==l.from&&e.mapPos(o.to,1)==l.to||(r|=8))}return this.visibleRanges=n,r}lineBlockAt(e){return e>=this.viewport.from&&e<=this.viewport.to&&this.viewportLines.find(t=>t.from<=e&&t.to>=e)||ro(this.heightMap.lineAt(e,he.ByPos,this.heightOracle,0,0),this.scaler)}lineBlockAtHeight(e){return e>=this.viewportLines[0].top&&e<=this.viewportLines[this.viewportLines.length-1].bottom&&this.viewportLines.find(t=>t.top<=e&&t.bottom>=e)||ro(this.heightMap.lineAt(this.scaler.fromDOM(e),he.ByHeight,this.heightOracle,0,0),this.scaler)}getScrollOffset(){return this.scrollParent==this.view.scrollDOM?this.scrollParent.scrollTop*this.scaleY:(this.scrollParent?this.scrollParent.getBoundingClientRect().top:0)-this.view.contentDOM.getBoundingClientRect().top}scrollAnchorAt(e){let t=this.lineBlockAtHeight(e+8);return t.from>=this.viewport.from||this.viewportLines[0].top-e>200?t:this.viewportLines[0]}elementAtHeight(e){return ro(this.heightMap.blockAt(this.scaler.fromDOM(e),this.heightOracle,0,0),this.scaler)}get docHeight(){return this.scaler.toDOM(this.heightMap.height)}get contentHeight(){return this.docHeight+this.paddingTop+this.paddingBottom}},zr=class{constructor(e,t){this.from=e,this.to=t}};function z$(i,e,t){let n=[],r=i,s=0;return G.spans(t,i,e,{span(){},point(o,l){o>r&&(n.push({from:r,to:o}),s+=o-r),r=l}},20),r<e&&(n.push({from:r,to:e}),s+=e-r),{total:s,ranges:n}}function xa({total:i,ranges:e},t){if(t<=0)return e[0].from;if(t>=1)return e[e.length-1].to;let n=Math.floor(i*t);for(let r=0;;r++){let{from:s,to:o}=e[r],l=o-s;if(n<=l)return s+n;n-=l}}function wa(i,e){let t=0;for(let{from:n,to:r}of i.ranges){if(e<=r){t+=e-n;break}t+=r-n}return t/i.total}function W$(i,e){for(let t of i)if(e(t))return t}var m0={toDOM(i){return i},fromDOM(i){return i},scale:1,eq(i){return i==this}};function b0(i){let e=i.facet(Ka).filter(n=>typeof n!="function"),t=i.facet(Hu).filter(n=>typeof n!="function");return t.length&&e.push(G.join(t)),e}var Cu=class i{constructor(e,t,n){let r=0,s=0,o=0;this.viewports=n.map(({from:l,to:a})=>{let c=t.lineAt(l,he.ByPos,e,0,0).top,h=t.lineAt(a,he.ByPos,e,0,0).bottom;return r+=h-c,{from:l,to:a,top:c,bottom:h,domTop:0,domBottom:0}}),this.scale=(7e6-r)/(t.height-r);for(let l of this.viewports)l.domTop=o+(l.top-s)*this.scale,o=l.domBottom=l.domTop+(l.bottom-l.top),s=l.bottom}toDOM(e){for(let t=0,n=0,r=0;;t++){let s=t<this.viewports.length?this.viewports[t]:null;if(!s||e<s.top)return r+(e-n)*this.scale;if(e<=s.bottom)return s.domTop+(e-s.top);n=s.bottom,r=s.domBottom}}fromDOM(e){for(let t=0,n=0,r=0;;t++){let s=t<this.viewports.length?this.viewports[t]:null;if(!s||e<s.domTop)return n+(e-r)/this.scale;if(e<=s.domBottom)return s.top+(e-s.domTop);n=s.bottom,r=s.domBottom}}eq(e){return e instanceof i?this.scale==e.scale&&this.viewports.length==e.viewports.length&&this.viewports.every((t,n)=>t.from==e.viewports[n].from&&t.to==e.viewports[n].to):!1}};function ro(i,e){if(e.scale==1)return i;let t=e.toDOM(i.top),n=e.toDOM(i.bottom);return new jt(i.from,i.length,t,n-t,Array.isArray(i._content)?i._content.map(r=>ro(r,e)):i._content)}var ka=R.define({combine:i=>i.join(" ")}),Tu=R.define({combine:i=>i.indexOf(!0)>-1}),Au=Tt.newName(),Q1=Tt.newName(),v1=Tt.newName(),$1={"&light":"."+Q1,"&dark":"."+v1};function Mu(i,e,t){return new Tt(e,{finish(n){return/&/.test(n)?n.replace(/&\w*/,r=>{if(r=="&")return i;if(!t||!t[r])throw new RangeError(`Unsupported selector: ${r}`);return t[r]}):i+" "+n}})}var N$=Mu("."+Au,{"&":{position:"relative !important",boxSizing:"border-box","&.cm-focused":{outline:"1px dotted #212121"},display:"flex !important",flexDirection:"column"},".cm-scroller":{display:"flex !important",alignItems:"flex-start !important",fontFamily:"monospace",lineHeight:1.4,height:"100%",overflowX:"auto",position:"relative",zIndex:0,overflowAnchor:"none"},".cm-content":{margin:0,flexGrow:2,flexShrink:0,display:"block",whiteSpace:"pre",wordWrap:"normal",boxSizing:"border-box",minHeight:"100%",padding:"4px 0",outline:"none","&[contenteditable=true]":{WebkitUserModify:"read-write-plaintext-only"}},".cm-lineWrapping":{whiteSpace_fallback:"pre-wrap",whiteSpace:"break-spaces",wordBreak:"break-word",overflowWrap:"anywhere",flexShrink:1},"&light .cm-content":{caretColor:"black"},"&dark .cm-content":{caretColor:"white"},".cm-line":{display:"block",padding:"0 2px 0 6px"},".cm-layer":{userSelect:"none",position:"absolute",left:0,top:0,contain:"size style","& > *":{position:"absolute"}},"&light .cm-selectionBackground":{background:"#d9d9d9"},"&dark .cm-selectionBackground":{background:"#222"},"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#d7d4f0"},"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#233"},".cm-cursorLayer":{pointerEvents:"none"},"&.cm-focused > .cm-scroller > .cm-cursorLayer":{animation:"steps(1) cm-blink 1.2s infinite"},"@keyframes cm-blink":{"0%":{},"50%":{opacity:0},"100%":{}},"@keyframes cm-blink2":{"0%":{},"50%":{opacity:0},"100%":{}},".cm-cursor, .cm-dropCursor":{borderLeft:"1.2px solid black",marginLeft:"-0.6px",pointerEvents:"none"},".cm-cursor":{display:"none"},"&dark .cm-cursor":{borderLeftColor:"#ddd"},".cm-selectionHandle":{backgroundColor:"currentColor",width:"1.5px"},".cm-selectionHandle-start::before, .cm-selectionHandle-end::before":{content:'""',backgroundColor:"inherit",borderRadius:"50%",width:"8px",height:"8px",position:"absolute",left:"-3.25px"},".cm-selectionHandle-start::before":{top:"-8px"},".cm-selectionHandle-end::before":{bottom:"-8px"},".cm-dropCursor":{position:"absolute"},"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor":{display:"block"},".cm-iso":{unicodeBidi:"isolate"},".cm-announced":{position:"fixed",top:"-10000px"},"@media print":{".cm-announced":{display:"none"}},"&light .cm-activeLine":{backgroundColor:"#cceeff44"},"&dark .cm-activeLine":{backgroundColor:"#99eeff33"},"&light .cm-specialChar":{color:"red"},"&dark .cm-specialChar":{color:"#f78"},".cm-gutters":{flexShrink:0,display:"flex",height:"100%",boxSizing:"border-box",zIndex:200},".cm-gutters-before":{insetInlineStart:0},".cm-gutters-after":{insetInlineEnd:0},"&light .cm-gutters":{backgroundColor:"#f5f5f5",color:"#6c6c6c",border:"0px solid #ddd","&.cm-gutters-before":{borderRightWidth:"1px"},"&.cm-gutters-after":{borderLeftWidth:"1px"}},"&dark .cm-gutters":{backgroundColor:"#333338",color:"#ccc"},".cm-gutter":{display:"flex !important",flexDirection:"column",flexShrink:0,boxSizing:"border-box",minHeight:"100%",overflow:"hidden"},".cm-gutterElement":{boxSizing:"border-box"},".cm-lineNumbers .cm-gutterElement":{padding:"0 3px 0 5px",minWidth:"20px",textAlign:"right",whiteSpace:"nowrap"},"&light .cm-activeLineGutter":{backgroundColor:"#e2f2ff"},"&dark .cm-activeLineGutter":{backgroundColor:"#222227"},".cm-panels":{boxSizing:"border-box",position:"sticky",left:0,right:0,zIndex:300},"&light .cm-panels":{backgroundColor:"#f5f5f5",color:"black"},".cm-panels-top":{top:"0"},".cm-panels-bottom":{bottom:"0"},"&light .cm-panels-top":{borderBottom:"1px solid #ddd"},"&light .cm-panels-bottom":{borderTop:"1px solid #ddd"},"&dark .cm-panels":{backgroundColor:"#333338",color:"white"},".cm-dialog":{padding:"2px 19px 4px 6px",position:"relative","& label":{fontSize:"80%"}},".cm-dialog-close":{position:"absolute",top:"3px",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",fontSize:"14px",padding:"0"},".cm-tab":{display:"inline-block",overflow:"hidden",verticalAlign:"bottom"},".cm-widgetBuffer":{verticalAlign:"text-top",height:"1em",width:0,display:"inline"},".cm-placeholder":{color:"#888",display:"inline-block",verticalAlign:"top",userSelect:"none"},".cm-highlightSpace":{background:"radial-gradient(circle at 50% 55%, #aaa 20%, transparent 0) no-repeat",backgroundSize:".4em",backgroundPosition:"calc(min(50%, 0px)) center"},".cm-highlightTab":{backgroundImage:`url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,backgroundSize:"auto 100%",backgroundPosition:"right 90%",backgroundRepeat:"no-repeat"},".cm-trailingSpace":{backgroundColor:"#ff332255"},".cm-button":{verticalAlign:"middle",color:"inherit",fontSize:"70%",padding:".2em 1em",borderRadius:"1px"},"&light .cm-button":{backgroundImage:"linear-gradient(#eff1f5, #d9d9df)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#b4b4b4, #d0d3d6)"}},"&dark .cm-button":{backgroundImage:"linear-gradient(#393939, #111)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#111, #333)"}},".cm-textfield":{verticalAlign:"middle",color:"inherit",fontSize:"70%",border:"1px solid silver",padding:".2em .5em"},"&light .cm-textfield":{backgroundColor:"white"},"&dark .cm-textfield":{border:"1px solid #555",backgroundColor:"inherit"}},$1),q$={childList:!0,characterData:!0,subtree:!0,attributes:!0,characterDataOldValue:!0},Wf=_.ie&&_.ie_version<=11,Ru=class{constructor(e){this.view=e,this.active=!1,this.editContext=null,this.selectionRange=new tu,this.selectionChanged=!1,this.delayedFlush=-1,this.resizeTimeout=-1,this.queue=[],this.delayedAndroidKey=null,this.flushingAndroidKey=-1,this.lastChange=0,this.scrollTargets=[],this.intersection=null,this.resizeScroll=null,this.intersecting=!1,this.gapIntersection=null,this.gaps=[],this.printQuery=null,this.parentCheck=-1,this.dom=e.contentDOM,this.observer=new MutationObserver(t=>{for(let n of t)this.queue.push(n);(_.ie&&_.ie_version<=11||_.ios&&e.composing)&&t.some(n=>n.type=="childList"&&n.removedNodes.length||n.type=="characterData"&&n.oldValue.length>n.target.nodeValue.length)?this.flushSoon():this.flush()}),window.EditContext&&_.android&&e.constructor.EDIT_CONTEXT!==!1&&!(_.chrome&&_.chrome_version<126)&&(this.editContext=new Xu(e),e.state.facet(xi)&&(e.contentDOM.editContext=this.editContext.editContext)),Wf&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.onResize=this.onResize.bind(this),this.onPrint=this.onPrint.bind(this),this.onScroll=this.onScroll.bind(this),window.matchMedia&&(this.printQuery=window.matchMedia("print")),typeof ResizeObserver=="function"&&(this.resizeScroll=new ResizeObserver(()=>{var t;((t=this.view.docView)===null||t===void 0?void 0:t.lastUpdate)<Date.now()-75&&this.onResize()}),this.resizeScroll.observe(e.scrollDOM)),this.addWindowListeners(this.win=e.win),this.start(),typeof IntersectionObserver=="function"&&(this.intersection=new IntersectionObserver(t=>{this.parentCheck<0&&(this.parentCheck=setTimeout(this.listenForScroll.bind(this),1e3)),t.length>0&&t[t.length-1].intersectionRatio>0!=this.intersecting&&(this.intersecting=!this.intersecting,this.intersecting!=this.view.inView&&this.onScrollChanged(document.createEvent("Event")))},{threshold:[0,.001]}),this.intersection.observe(this.dom),this.gapIntersection=new IntersectionObserver(t=>{t.length>0&&t[t.length-1].intersectionRatio>0&&this.onScrollChanged(document.createEvent("Event"))},{})),this.listenForScroll(),this.readSelectionRange()}onScrollChanged(e){this.view.inputState.runHandlers("scroll",e),this.intersecting&&this.view.measure()}onScroll(e){this.intersecting&&this.flush(!1),this.editContext&&this.view.requestMeasure(this.editContext.measureReq),this.onScrollChanged(e)}onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout(()=>{this.resizeTimeout=-1,this.view.requestMeasure()},50))}onPrint(e){(e.type=="change"||!e.type)&&!e.matches||(this.view.viewState.printing=!0,this.view.measure(),setTimeout(()=>{this.view.viewState.printing=!1,this.view.requestMeasure()},500))}updateGaps(e){if(this.gapIntersection&&(e.length!=this.gaps.length||this.gaps.some((t,n)=>t!=e[n]))){this.gapIntersection.disconnect();for(let t of e)this.gapIntersection.observe(t);this.gaps=e}}onSelectionChange(e){let t=this.selectionChanged;if(!this.readSelectionRange()||this.delayedAndroidKey)return;let{view:n}=this,r=this.selectionRange;if(n.state.facet(xi)?n.root.activeElement!=this.dom:!oo(this.dom,r))return;let s=r.anchorNode&&n.docView.tile.nearest(r.anchorNode);if(s&&s.isWidget()&&s.widget.ignoreEvent(e)){t||(this.selectionChanged=!1);return}(_.ie&&_.ie_version<=11||_.android&&_.chrome)&&!n.state.selection.main.empty&&r.focusNode&&lo(r.focusNode,r.focusOffset,r.anchorNode,r.anchorOffset)?this.flushSoon():this.flush(!1)}readSelectionRange(){let{view:e}=this,t=bo(e.root);if(!t)return!1;let n=_.safari&&e.root.nodeType==11&&e.root.activeElement==this.dom&&G$(this.view,t)||t;if(!n||this.selectionRange.eq(n))return!1;let r=oo(this.dom,n);return r&&!this.selectionChanged&&e.inputState.lastFocusTime>Date.now()-200&&e.inputState.lastTouchTime<Date.now()-300&&Vv(this.dom,n)?(this.view.inputState.lastFocusTime=0,e.docView.updateSelection(),!1):(this.selectionRange.setRange(n),r&&(this.selectionChanged=!0),!0)}setSelectionRange(e,t){this.selectionRange.set(e.node,e.offset,t.node,t.offset),this.selectionChanged=!1}clearSelectionRange(){this.selectionRange.set(null,0,null,0)}listenForScroll(){this.parentCheck=-1;let e=0,t=null;for(let n=this.dom;n;)if(n.nodeType==1)!t&&e<this.scrollTargets.length&&this.scrollTargets[e]==n?e++:t||(t=this.scrollTargets.slice(0,e)),t&&t.push(n),n=n.assignedSlot||n.parentNode;else if(n.nodeType==11)n=n.host;else break;if(e<this.scrollTargets.length&&!t&&(t=this.scrollTargets.slice(0,e)),t){for(let n of this.scrollTargets)n.removeEventListener("scroll",this.onScroll);for(let n of this.scrollTargets=t)n.addEventListener("scroll",this.onScroll)}}ignore(e){if(!this.active)return e();try{return this.stop(),e()}finally{this.start(),this.clear()}}start(){this.active||(this.observer.observe(this.dom,q$),Wf&&this.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.active=!0)}stop(){this.active&&(this.active=!1,this.observer.disconnect(),Wf&&this.dom.removeEventListener("DOMCharacterDataModified",this.onCharData))}clear(){this.processRecords(),this.queue.length=0,this.selectionChanged=!1}delayAndroidKey(e,t){var n;if(!this.delayedAndroidKey){let r=()=>{let s=this.delayedAndroidKey;s&&(this.clearDelayedAndroidKey(),this.view.inputState.lastKeyCode=s.keyCode,this.view.inputState.lastKeyTime=Date.now(),!this.flush()&&s.force&&Gr(this.dom,s.key,s.keyCode))};this.flushingAndroidKey=this.view.win.requestAnimationFrame(r)}(!this.delayedAndroidKey||e=="Enter")&&(this.delayedAndroidKey={key:e,keyCode:t,force:this.lastChange<Date.now()-50||!!(!((n=this.delayedAndroidKey)===null||n===void 0)&&n.force)})}clearDelayedAndroidKey(){this.win.cancelAnimationFrame(this.flushingAndroidKey),this.delayedAndroidKey=null,this.flushingAndroidKey=-1}flushSoon(){this.delayedFlush<0&&(this.delayedFlush=this.view.win.requestAnimationFrame(()=>{this.delayedFlush=-1,this.flush()}))}forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame(this.delayedFlush),this.delayedFlush=-1),this.flush()}pendingRecords(){for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}processRecords(){let e=this.pendingRecords();e.length&&(this.queue=[]);let t=-1,n=-1,r=!1;for(let s of e){let o=this.readMutation(s);o&&(o.typeOver&&(r=!0),t==-1?{from:t,to:n}=o:(t=Math.min(o.from,t),n=Math.max(o.to,n)))}return{from:t,to:n,typeOver:r}}readChange(){let{from:e,to:t,typeOver:n}=this.processRecords(),r=this.selectionChanged&&oo(this.dom,this.selectionRange);if(e<0&&!r)return null;e>-1&&(this.lastChange=Date.now()),this.view.inputState.lastFocusTime=0,this.selectionChanged=!1;let s=new bu(this.view,e,t,n);return this.view.docView.domChanged={newSel:s.newSel?s.newSel.main:null},s}flush(e=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;e&&this.readSelectionRange();let t=this.readChange();if(!t)return this.view.requestMeasure(),!1;let n=this.view.state,r=p1(this.view,t);return this.view.state==n&&(t.domChanged||t.newSel&&!Ua(this.view.state.selection,t.newSel.main))&&this.view.update([]),r}readMutation(e){let t=this.view.docView.tile.nearest(e.target);if(!t||t.isWidget())return null;if(t.markDirty(e.type=="attributes"),e.type=="childList"){let n=y0(t,e.previousSibling||e.target.previousSibling,-1),r=y0(t,e.nextSibling||e.target.nextSibling,1);return{from:n?t.posAfter(n):t.posAtStart,to:r?t.posBefore(r):t.posAtEnd,typeOver:!1}}else return e.type=="characterData"?{from:t.posAtStart,to:t.posAtEnd,typeOver:e.target.nodeValue==e.oldValue}:null}setWindow(e){e!=this.win&&(this.removeWindowListeners(this.win),this.win=e,this.addWindowListeners(this.win))}addWindowListeners(e){e.addEventListener("resize",this.onResize),this.printQuery?this.printQuery.addEventListener?this.printQuery.addEventListener("change",this.onPrint):this.printQuery.addListener(this.onPrint):e.addEventListener("beforeprint",this.onPrint),e.addEventListener("scroll",this.onScroll),e.document.addEventListener("selectionchange",this.onSelectionChange)}removeWindowListeners(e){e.removeEventListener("scroll",this.onScroll),e.removeEventListener("resize",this.onResize),this.printQuery?this.printQuery.removeEventListener?this.printQuery.removeEventListener("change",this.onPrint):this.printQuery.removeListener(this.onPrint):e.removeEventListener("beforeprint",this.onPrint),e.document.removeEventListener("selectionchange",this.onSelectionChange)}update(e){this.editContext&&(this.editContext.update(e),e.startState.facet(xi)!=e.state.facet(xi)&&(e.view.contentDOM.editContext=e.state.facet(xi)?this.editContext.editContext:null))}destroy(){var e,t,n;this.stop(),(e=this.intersection)===null||e===void 0||e.disconnect(),(t=this.gapIntersection)===null||t===void 0||t.disconnect(),(n=this.resizeScroll)===null||n===void 0||n.disconnect();for(let r of this.scrollTargets)r.removeEventListener("scroll",this.onScroll);this.removeWindowListeners(this.win),clearTimeout(this.parentCheck),clearTimeout(this.resizeTimeout),this.win.cancelAnimationFrame(this.delayedFlush),this.win.cancelAnimationFrame(this.flushingAndroidKey),this.editContext&&(this.view.contentDOM.editContext=null,this.editContext.destroy())}};function y0(i,e,t){for(;e;){let n=fe.get(e);if(n&&n.parent==i)return n;let r=e.parentNode;e=r!=i.dom?r:t>0?e.nextSibling:e.previousSibling}return null}function S0(i,e){let t=e.startContainer,n=e.startOffset,r=e.endContainer,s=e.endOffset,o=i.docView.domAtPos(i.state.selection.main.anchor,1);return lo(o.node,o.offset,r,s)&&([t,n,r,s]=[r,s,t,n]),{anchorNode:t,anchorOffset:n,focusNode:r,focusOffset:s}}function G$(i,e){if(e.getComposedRanges){let r=e.getComposedRanges(i.root)[0];if(r)return S0(i,r)}let t=null;function n(r){r.preventDefault(),r.stopImmediatePropagation(),t=r.getTargetRanges()[0]}return i.contentDOM.addEventListener("beforeinput",n,!0),i.dom.ownerDocument.execCommand("indent"),i.contentDOM.removeEventListener("beforeinput",n,!0),t?S0(i,t):null}var Xu=class{constructor(e){this.from=0,this.to=0,this.pendingContextChange=null,this.handlers=Object.create(null),this.composing=null,this.resetRange(e.state);let t=this.editContext=new window.EditContext({text:e.state.doc.sliceString(this.from,this.to),selectionStart:this.toContextPos(Math.max(this.from,Math.min(this.to,e.state.selection.main.anchor))),selectionEnd:this.toContextPos(e.state.selection.main.head)});this.handlers.textupdate=n=>{let r=e.state.selection.main,{anchor:s,head:o}=r,l=this.toEditorPos(n.updateRangeStart),a=this.toEditorPos(n.updateRangeEnd);e.inputState.composing>=0&&!this.composing&&(this.composing={contextBase:n.updateRangeStart,editorBase:l,drifted:!1});let c=a-l>n.text.length;l==this.from&&s<this.from?l=s:a==this.to&&s>this.to&&(a=s);let h=O1(e.state.sliceDoc(l,a),n.text,(c?r.from:r.to)-l,c?"end":null);if(!h){let u=v.single(this.toEditorPos(n.selectionStart),this.toEditorPos(n.selectionEnd));Ua(u,r)||e.dispatch({selection:u,userEvent:"select"});return}let f={from:h.from+l,to:h.toA+l,insert:q.of(n.text.slice(h.from,h.toB).split(` +`))};if((_.mac||_.android)&&f.from==o-1&&/^\. ?$/.test(n.text)&&e.contentDOM.getAttribute("autocorrect")=="off"&&(f={from:l,to:a,insert:q.of([n.text.replace("."," ")])}),this.pendingContextChange=f,!e.state.readOnly){let u=this.to-this.from+(f.to-f.from+f.insert.length);Ju(e,f,v.single(this.toEditorPos(n.selectionStart,u),this.toEditorPos(n.selectionEnd,u)))}this.pendingContextChange&&(this.revertPending(e.state),this.setSelection(e.state)),f.from<f.to&&!f.insert.length&&e.inputState.composing>=0&&!/[\\p{Alphabetic}\\p{Number}_]/.test(t.text.slice(Math.max(0,n.updateRangeStart-1),Math.min(t.text.length,n.updateRangeStart+1)))&&this.handlers.compositionend(n)},this.handlers.characterboundsupdate=n=>{let r=[],s=null;for(let o=this.toEditorPos(n.rangeStart),l=this.toEditorPos(n.rangeEnd);o<l;o++){let a=e.coordsForChar(o);s=a&&new DOMRect(a.left,a.top,a.right-a.left,a.bottom-a.top)||s||new DOMRect,r.push(s)}t.updateCharacterBounds(n.rangeStart,r)},this.handlers.textformatupdate=n=>{let r=[];for(let s of n.getTextFormats()){let o=s.underlineStyle,l=s.underlineThickness;if(!/none/i.test(o)&&!/none/i.test(l)){let a=this.toEditorPos(s.rangeStart),c=this.toEditorPos(s.rangeEnd);if(a<c){let h=`text-decoration: underline ${/^[a-z]/.test(o)?o+" ":o=="Dashed"?"dashed ":o=="Squiggle"?"wavy ":""}${/thin/i.test(l)?1:2}px`;r.push(L.mark({attributes:{style:h}}).range(a,c))}}}e.dispatch({effects:s1.of(L.set(r))})},this.handlers.compositionstart=()=>{e.inputState.composing<0&&(e.inputState.composing=0,e.inputState.compositionFirstChange=!0)},this.handlers.compositionend=()=>{if(e.inputState.composing=-1,e.inputState.compositionFirstChange=null,this.composing){let{drifted:n}=this.composing;this.composing=null,n&&this.reset(e.state)}};for(let n in this.handlers)t.addEventListener(n,this.handlers[n]);this.measureReq={read:n=>{let r=bo(n.root);r&&r.rangeCount&&this.editContext.updateSelectionBounds(r.getRangeAt(0).getBoundingClientRect())}}}applyEdits(e){let t=0,n=!1,r=this.pendingContextChange;return e.changes.iterChanges((s,o,l,a,c)=>{if(n)return;let h=c.length-(o-s);if(r&&o>=r.to)if(r.from==s&&r.to==o&&r.insert.eq(c)){r=this.pendingContextChange=null,t+=h,this.to+=h;return}else r=null,this.revertPending(e.state);if(s+=t,o+=t,o<=this.from)this.from+=h,this.to+=h;else if(s<this.to){if(s<this.from||o>this.to||this.to-this.from+c.length>3e4){n=!0;return}this.editContext.updateText(this.toContextPos(s),this.toContextPos(o),c.toString()),this.to+=h}t+=h}),r&&!n&&this.revertPending(e.state),!n}update(e){let t=this.pendingContextChange,n=e.startState.selection.main;this.composing&&(this.composing.drifted||!e.changes.touchesRange(n.from,n.to)&&e.transactions.some(r=>!r.isUserEvent("input.type")&&r.changes.touchesRange(this.from,this.to)))?(this.composing.drifted=!0,this.composing.editorBase=e.changes.mapPos(this.composing.editorBase)):!this.applyEdits(e)||!this.rangeIsValid(e.state)?(this.pendingContextChange=null,this.reset(e.state)):(e.docChanged||e.selectionSet||t)&&this.setSelection(e.state),(e.geometryChanged||e.docChanged||e.selectionSet)&&e.view.requestMeasure(this.measureReq)}resetRange(e){let{head:t}=e.selection.main;this.from=Math.max(0,t-1e4),this.to=Math.min(e.doc.length,t+1e4)}reset(e){this.resetRange(e),this.editContext.updateText(0,this.editContext.text.length,e.doc.sliceString(this.from,this.to)),this.setSelection(e)}revertPending(e){let t=this.pendingContextChange;this.pendingContextChange=null,this.editContext.updateText(this.toContextPos(t.from),this.toContextPos(t.from+t.insert.length),e.doc.sliceString(t.from,t.to))}setSelection(e){let{main:t}=e.selection,n=this.toContextPos(Math.max(this.from,Math.min(this.to,t.anchor))),r=this.toContextPos(t.head);(this.editContext.selectionStart!=n||this.editContext.selectionEnd!=r)&&this.editContext.updateSelection(n,r)}rangeIsValid(e){let{head:t}=e.selection.main;return!(this.from>0&&t-this.from<500||this.to<e.doc.length&&this.to-t<500||this.to-this.from>1e4*3)}toEditorPos(e,t=this.to-this.from){e=Math.min(e,t);let n=this.composing;return n&&n.drifted?n.editorBase+(e-n.contextBase):e+this.from}toContextPos(e){let t=this.composing;return t&&t.drifted?t.contextBase+(e-t.editorBase):e-this.from}destroy(){for(let e in this.handlers)this.editContext.removeEventListener(e,this.handlers[e])}},M=class i{get state(){return this.viewState.state}get viewport(){return this.viewState.viewport}get visibleRanges(){return this.viewState.visibleRanges}get inView(){return this.viewState.inView}get composing(){return!!this.inputState&&this.inputState.composing>0}get compositionStarted(){return!!this.inputState&&this.inputState.composing>=0}get root(){return this._root}get win(){return this.dom.ownerDocument.defaultView||window}constructor(e={}){var t;this.plugins=[],this.pluginMap=new Map,this.editorAttrs={},this.contentAttrs={},this.bidiCache=[],this.destroyed=!1,this.updateState=2,this.measureScheduled=-1,this.measureRequests=[],this.clearAnnouncement=-1,this.contentDOM=document.createElement("div"),this.scrollDOM=document.createElement("div"),this.scrollDOM.tabIndex=-1,this.scrollDOM.className="cm-scroller",this.scrollDOM.appendChild(this.contentDOM),this.announceDOM=document.createElement("div"),this.announceDOM.className="cm-announced",this.announceDOM.setAttribute("aria-live","polite"),this.dom=document.createElement("div"),this.dom.appendChild(this.announceDOM),this.dom.appendChild(this.scrollDOM),e.parent&&e.parent.appendChild(this.dom);let{dispatch:n}=e;this.dispatchTransactions=e.dispatchTransactions||n&&(r=>r.forEach(s=>n(s,this)))||(r=>this.update(r)),this.dispatch=this.dispatch.bind(this),this._root=e.root||Bv(e.parent)||document,this.viewState=new Ya(this,e.state||ne.create(e)),e.scrollTo&&e.scrollTo.is(ba)&&(this.viewState.scrollTarget=e.scrollTo.value.clip(this.viewState.state)),this.plugins=this.state.facet(Yr).map(r=>new co(r));for(let r of this.plugins)r.update(this);this.observer=new Ru(this),this.inputState=new yu(this),this.inputState.ensureHandlers(this.plugins),this.docView=new ja(this),this.mountStyles(),this.updateAttrs(),this.updateState=0,this.requestMeasure(),!((t=document.fonts)===null||t===void 0)&&t.ready&&document.fonts.ready.then(()=>{this.viewState.mustMeasureContent="refresh",this.requestMeasure()})}dispatch(...e){let t=e.length==1&&e[0]instanceof xe?e:e.length==1&&Array.isArray(e[0])?e[0]:[this.state.update(...e)];this.dispatchTransactions(t,this)}update(e){if(this.updateState!=0)throw new Error("Calls to EditorView.update are not allowed while an update is in progress");let t=!1,n=!1,r,s=this.state;for(let u of e){if(u.startState!=s)throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");s=u.state}if(this.destroyed){this.viewState.state=s;return}let o=this.hasFocus,l=0,a=null;e.some(u=>u.annotation(x1))?(this.inputState.notifiedFocused=o,l=1):o!=this.inputState.notifiedFocused&&(this.inputState.notifiedFocused=o,a=w1(s,o),a||(l=1));let c=this.observer.delayedAndroidKey,h=null;if(c?(this.observer.clearDelayedAndroidKey(),h=this.observer.readChange(),(h&&!this.state.doc.eq(s.doc)||!this.state.selection.eq(s.selection))&&(h=null)):this.observer.clear(),s.facet(ne.phrases)!=this.state.facet(ne.phrases))return this.setState(s);r=La.create(this,s,e),r.flags|=l;let f=this.viewState.scrollTarget;try{this.updateState=2;for(let u of e){if(f&&(f=f.map(u.changes)),u.scrollIntoView){let{main:d}=u.state.selection,{x:p,y:O}=this.state.facet(i.cursorScrollMargin);f=new ao(d.empty?d:v.cursor(d.head,d.head>d.anchor?-1:1),"nearest","nearest",O,p)}for(let d of u.effects)d.is(ba)&&(f=d.value.clip(this.state))}this.viewState.update(r,f),this.bidiCache=za.update(this.bidiCache,r.changes),r.empty||(this.updatePlugins(r),this.inputState.update(r)),t=this.docView.update(r),this.state.facet(no)!=this.styleModules&&this.mountStyles(),n=this.updateAttrs(),this.showAnnouncements(e),this.docView.updateSelection(t,e.some(u=>u.isUserEvent("select.pointer")))}finally{this.updateState=0}if(r.startState.facet(ka)!=r.state.facet(ka)&&(this.viewState.mustMeasureContent=!0),(t||n||f||this.viewState.mustEnforceCursorAssoc||this.viewState.mustMeasureContent)&&this.requestMeasure(),t&&this.docViewUpdate(),!r.empty)for(let u of this.state.facet(su))try{u(r)}catch(d){_e(this.state,d,"update listener")}(a||h)&&Promise.resolve().then(()=>{a&&this.state==a.startState&&this.dispatch(a),h&&!p1(this,h)&&c.force&&Gr(this.contentDOM,c.key,c.keyCode)})}setState(e){if(this.updateState!=0)throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");if(this.destroyed){this.viewState.state=e;return}this.updateState=2;let t=this.hasFocus;try{for(let n of this.plugins)n.destroy(this);this.viewState=new Ya(this,e),this.plugins=e.facet(Yr).map(n=>new co(n)),this.pluginMap.clear();for(let n of this.plugins)n.update(this);this.docView.destroy(),this.docView=new ja(this),this.inputState.ensureHandlers(this.plugins),this.mountStyles(),this.updateAttrs(),this.bidiCache=[]}finally{this.updateState=0}t&&this.focus(),this.requestMeasure()}updatePlugins(e){let t=e.startState.facet(Yr),n=e.state.facet(Yr);if(t!=n){let r=[];for(let s of n){let o=t.indexOf(s);if(o<0)r.push(new co(s));else{let l=this.plugins[o];l.mustUpdate=e,r.push(l)}}for(let s of this.plugins)s.mustUpdate!=e&&s.destroy(this);this.plugins=r,this.pluginMap.clear()}else for(let r of this.plugins)r.mustUpdate=e;for(let r=0;r<this.plugins.length;r++)this.plugins[r].update(this);t!=n&&this.inputState.ensureHandlers(this.plugins)}docViewUpdate(){for(let e of this.plugins){let t=e.value;if(t&&t.docViewUpdate)try{t.docViewUpdate(this)}catch(n){_e(this.state,n,"doc view update listener")}}}measure(e=!0){if(this.destroyed)return;if(this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.observer.delayedAndroidKey){this.measureScheduled=-1,this.requestMeasure();return}this.measureScheduled=0,e&&this.observer.forceFlush();let t=null,n=this.viewState.scrollParent,r=this.viewState.getScrollOffset(),{scrollAnchorPos:s,scrollAnchorHeight:o,scaleY:l}=this.viewState;Math.abs(r-this.viewState.scrollOffset)>1&&(o=-1),this.viewState.scrollAnchorHeight=-1;try{for(let a=0;;a++){if(o<0){if(B0(n||this.win))s=-1,o=this.viewState.heightMap.height/this.viewState.scaleY;else{let p=this.viewState.scrollAnchorAt(r);s=p.from,o=p.top}l=this.viewState.scaleY}this.updateState=1;let c=this.viewState.measure();if(!c&&!this.measureRequests.length&&this.viewState.scrollTarget==null)break;if(a>5){console.warn(this.measureRequests.length?"Measure loop restarted more than 5 times":"Viewport failed to stabilize");break}let h=[];c&4||([this.measureRequests,h]=[h,this.measureRequests]);let f=h.map(p=>{try{return p.read(this)}catch(O){return _e(this.state,O),x0}}),u=La.create(this,this.state,[]),d=!1;u.flags|=c,t?t.flags|=c:t=u,this.updateState=2,u.empty||(this.updatePlugins(u),this.inputState.update(u),this.updateAttrs(),d=this.docView.update(u),d&&this.docViewUpdate());for(let p=0;p<h.length;p++)if(f[p]!=x0)try{let O=h[p];O.write&&O.write(f[p],this)}catch(O){_e(this.state,O)}if(d&&this.docView.updateSelection(!0),!u.viewportChanged&&this.measureRequests.length==0){if(this.viewState.editorHeight)if(this.viewState.scrollTarget){this.docView.scrollIntoView(this.viewState.scrollTarget),this.viewState.scrollTarget=null,o=-1;continue}else{let O=(s<0?this.viewState.heightMap.height:this.viewState.lineBlockAt(s).top)/this.viewState.scaleY-o/l;if((O>1||O<-1)&&!(_.ios&&this.inputState.lastIOSMomentumScroll>Date.now()-100)&&(n==this.scrollDOM||this.hasFocus||Math.max(this.inputState.lastWheelEvent,this.inputState.lastTouchTime)>Date.now()-100)){r=r+O,n?s<0?n.scrollTop=n.scrollHeight:n.scrollTop+=O:this.win.scrollBy(0,O),o=-1;continue}}break}}}finally{this.updateState=0,this.measureScheduled=-1}if(t&&!t.empty)for(let a of this.state.facet(su))a(t)}get themeClasses(){return Au+" "+(this.state.facet(Tu)?v1:Q1)+" "+this.state.facet(ka)}updateAttrs(){let e=w0(this,o1,{class:"cm-editor"+(this.hasFocus?" cm-focused ":" ")+this.themeClasses}),t={spellcheck:"false",autocorrect:"off",autocapitalize:"off",writingsuggestions:"false",translate:"no",contenteditable:this.state.facet(xi)?"true":"false",class:"cm-content",style:`${_.tabSize}: ${this.state.tabSize}`,role:"textbox","aria-multiline":"true"};this.state.readOnly&&(t["aria-readonly"]="true"),w0(this,Fu,t);let n=this.observer.ignore(()=>{let r=e0(this.contentDOM,this.contentAttrs,t),s=e0(this.dom,this.editorAttrs,e);return r||s});return this.editorAttrs=e,this.contentAttrs=t,n}showAnnouncements(e){let t=!0;for(let n of e)for(let r of n.effects)if(r.is(i.announce)){t&&(this.announceDOM.textContent="",this.win.clearTimeout(this.clearAnnouncement),this.clearAnnouncement=this.win.setTimeout(()=>{this.announceDOM.textContent="\xA0"},200),t=!1);let s=this.announceDOM.appendChild(document.createElement("div"));s.textContent=r.value}}mountStyles(){this.styleModules=this.state.facet(no);let e=this.state.facet(i.cspNonce);Tt.mount(this.root,this.styleModules.concat(N$).reverse(),e?{nonce:e}:void 0)}readMeasured(){if(this.updateState==2)throw new Error("Reading the editor layout isn't allowed during an update");this.updateState==0&&this.measureScheduled>-1&&this.measure(!1)}requestMeasure(e){if(this.measureScheduled<0&&(this.measureScheduled=this.win.requestAnimationFrame(()=>this.measure())),e){if(this.measureRequests.indexOf(e)>-1)return;if(e.key!=null){for(let t=0;t<this.measureRequests.length;t++)if(this.measureRequests[t].key===e.key){this.measureRequests[t]=e;return}}this.measureRequests.push(e)}}plugin(e){let t=this.pluginMap.get(e);return(t===void 0||t&&t.plugin!=e)&&this.pluginMap.set(e,t=this.plugins.find(n=>n.plugin==e)||null),t&&t.update(this).value}get documentTop(){return this.contentDOM.getBoundingClientRect().top+this.viewState.paddingTop}get documentPadding(){return{top:this.viewState.paddingTop,bottom:this.viewState.paddingBottom}}get scaleX(){return this.viewState.scaleX}get scaleY(){return this.viewState.scaleY}elementAtHeight(e){return this.readMeasured(),this.viewState.elementAtHeight(e)}lineBlockAtHeight(e){return this.readMeasured(),this.viewState.lineBlockAtHeight(e)}get viewportLineBlocks(){return this.viewState.viewportLines}lineBlockAt(e){return this.viewState.lineBlockAt(e)}get contentHeight(){return this.viewState.contentHeight}moveByChar(e,t,n){return zf(this,e,o0(this,e,t,n))}moveByGroup(e,t){return zf(this,e,o0(this,e,t,n=>m$(this,e.head,n)))}visualLineSide(e,t){return t?v.cursor(e.to,1):v.cursor(e.from,-1)}moveToLineBoundary(e,t,n=!0){return g$(this,e,t,n)}moveVertically(e,t,n){return zf(this,e,b$(this,e,t,n))}domAtPos(e,t=1){return this.docView.domAtPos(e,t)}posAtDOM(e,t=0){return this.docView.posFromDOM(e,t)}posAtCoords(e,t=!0){this.readMeasured();let n=Ou(this,e,t);return n&&n.pos}posAndSideAtCoords(e,t=!0){return this.readMeasured(),Ou(this,e,t)}coordsAtPos(e,t=1){this.readMeasured();let n=this.state.doc.lineAt(e),r=this.bidiSpans(n),s=r[Rt.find(r,e-n.from,-1,t)];return n.length&&(e==n.from&&t<0||e==n.to&&t>0)&&s.dir!=this.textDirectionAt(n.from)&&(e==n.to?(e=n.from+s.from,t=1):(e=n.from+s.to,t=-1)),this.docView.coordsAt(e,t,s.dir==re.RTL)}coordsForChar(e){return this.readMeasured(),this.docView.coordsForChar(e)}get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}get textDirection(){return this.viewState.defaultTextDirection}textDirectionAt(e){return!this.state.facet(i1)||e<this.viewport.from||e>this.viewport.to?this.textDirection:(this.readMeasured(),this.docView.textDirectionAt(e))}get lineWrapping(){return this.viewState.heightOracle.lineWrapping}bidiSpans(e){if(e.length>F$)return q0(e.length);let t=this.textDirectionAt(e.from),n;for(let s of this.bidiCache)if(s.from==e.from&&s.dir==t&&(s.fresh||N0(s.isolates,n=n0(this,e))))return s.order;n||(n=n0(this,e));let r=Fv(e.text,t,n);return this.bidiCache.push(new za(e.from,e.to,t,n,!0,r)),r}get hasFocus(){var e;return(this.dom.ownerDocument.hasFocus()||_.safari&&((e=this.inputState)===null||e===void 0?void 0:e.lastContextMenu)>Date.now()-3e4)&&this.root.activeElement==this.contentDOM}focus(){this.observer.ignore(()=>{U0(this.contentDOM),this.docView.updateSelection()})}setRoot(e){this._root!=e&&(this._root=e,this.observer.setWindow((e.nodeType==9?e:e.ownerDocument).defaultView||window),this.mountStyles())}destroy(){this.root.activeElement==this.contentDOM&&this.contentDOM.blur();for(let e of this.plugins)e.destroy(this);this.plugins=[],this.inputState.destroy(),this.docView.destroy(),this.dom.remove(),this.observer.destroy(),this.win.clearTimeout(this.clearAnnouncement),this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.destroyed=!0}static scrollIntoView(e,t={}){var n,r,s,o;return ba.of(new ao(typeof e=="number"?v.cursor(e):e,(n=t.y)!==null&&n!==void 0?n:"nearest",(r=t.x)!==null&&r!==void 0?r:"nearest",(s=t.yMargin)!==null&&s!==void 0?s:5,(o=t.xMargin)!==null&&o!==void 0?o:5))}scrollSnapshot(){let{scrollTop:e,scrollLeft:t}=this.scrollDOM,n=this.viewState.scrollAnchorAt(e);return ba.of(new ao(v.cursor(n.from),"start","start",n.top-e,t,!0))}setTabFocusMode(e){e==null?this.inputState.tabFocusMode=this.inputState.tabFocusMode<0?0:-1:typeof e=="boolean"?this.inputState.tabFocusMode=e?0:-1:this.inputState.tabFocusMode!=0&&(this.inputState.tabFocusMode=Date.now()+e)}static domEventHandlers(e){return se.define(()=>({}),{eventHandlers:e})}static domEventObservers(e){return se.define(()=>({}),{eventObservers:e})}static theme(e,t){let n=Tt.newName(),r=[ka.of(n),no.of(Mu(`.${n}`,e))];return t&&t.dark&&r.push(Tu.of(!0)),r}static baseTheme(e){return at.lowest(no.of(Mu("."+Au,e,$1)))}static findFromDOM(e){var t;let n=e.querySelector(".cm-content"),r=n&&fe.get(n)||fe.get(e);return((t=r?.root)===null||t===void 0?void 0:t.view)||null}};M.styleModule=no;M.inputHandler=e1;M.clipboardInputFilter=qu;M.clipboardOutputFilter=Gu;M.scrollHandler=r1;M.focusChangeEffect=t1;M.perLineTextDirection=i1;M.exceptionSink=J0;M.updateListener=su;M.editable=xi;M.mouseSelectionStyle=K0;M.dragMovesSelection=H0;M.clickAddsSelectionRange=F0;M.decorations=Ka;M.blockWrappers=l1;M.outerDecorations=Hu;M.atomicRanges=So;M.bidiIsolatedRanges=a1;M.cursorScrollMargin=R.define({combine:i=>{let e=5,t=5;for(let n of i)typeof n=="number"?e=t=n:{x:e,y:t}=n;return{x:e,y:t}}});M.scrollMargins=c1;M.darkTheme=Tu;M.cspNonce=R.define({combine:i=>i.length?i[0]:""});M.contentAttributes=Fu;M.editorAttributes=o1;M.lineWrapping=M.contentAttributes.of({class:"cm-lineWrapping"});M.announce=Y.define();var F$=4096,x0={},za=class i{constructor(e,t,n,r,s,o){this.from=e,this.to=t,this.dir=n,this.isolates=r,this.fresh=s,this.order=o}static update(e,t){if(t.empty&&!e.some(s=>s.fresh))return e;let n=[],r=e.length?e[e.length-1].dir:re.LTR;for(let s=Math.max(0,e.length-10);s<e.length;s++){let o=e[s];o.dir==r&&!t.touchesRange(o.from,o.to)&&n.push(new i(t.mapPos(o.from,1),t.mapPos(o.to,-1),o.dir,o.isolates,!1,o.order))}return n}};function w0(i,e,t){for(let n=i.state.facet(e),r=n.length-1;r>=0;r--){let s=n[r],o=typeof s=="function"?s(i):s;o&&zu(o,t)}return t}var H$=_.mac?"mac":_.windows?"win":_.linux?"linux":"key";function K$(i,e){let t=i.split(/-(?!$)/),n=t[t.length-1];n=="Space"&&(n=" ");let r,s,o,l;for(let a=0;a<t.length-1;++a){let c=t[a];if(/^(cmd|meta|m)$/i.test(c))l=!0;else if(/^a(lt)?$/i.test(c))r=!0;else if(/^(c|ctrl|control)$/i.test(c))s=!0;else if(/^s(hift)?$/i.test(c))o=!0;else if(/^mod$/i.test(c))e=="mac"?l=!0:s=!0;else throw new Error("Unrecognized modifier name: "+c)}return r&&(n="Alt-"+n),s&&(n="Ctrl-"+n),l&&(n="Meta-"+n),o&&(n="Shift-"+n),n}function Qa(i,e,t){return e.altKey&&(i="Alt-"+i),e.ctrlKey&&(i="Ctrl-"+i),e.metaKey&&(i="Meta-"+i),t!==!1&&e.shiftKey&&(i="Shift-"+i),i}var J$=at.default(M.domEventHandlers({keydown(i,e){return T1(P1(e.state),i,e,"editor")}})),Qi=R.define({enables:J$}),k0=new WeakMap;function P1(i){let e=i.facet(Qi),t=k0.get(e);return t||k0.set(e,t=tP(e.reduce((n,r)=>n.concat(r),[]))),t}function C1(i,e,t){return T1(P1(i.state),e,i,t)}var Ii=null,eP=4e3;function tP(i,e=H$){let t=Object.create(null),n=Object.create(null),r=(o,l)=>{let a=n[o];if(a==null)n[o]=l;else if(a!=l)throw new Error("Key binding "+o+" is used both as a regular binding and as a multi-stroke prefix")},s=(o,l,a,c,h)=>{var f,u;let d=t[o]||(t[o]=Object.create(null)),p=l.split(/ (?!$)/).map(b=>K$(b,e));for(let b=1;b<p.length;b++){let w=p.slice(0,b).join(" ");r(w,!0),d[w]||(d[w]={preventDefault:!0,stopPropagation:!1,run:[x=>{let X=Ii={view:x,prefix:w,scope:o};return setTimeout(()=>{Ii==X&&(Ii=null)},eP),!0}]})}let O=p.join(" ");r(O,!1);let g=d[O]||(d[O]={preventDefault:!1,stopPropagation:!1,run:((u=(f=d._any)===null||f===void 0?void 0:f.run)===null||u===void 0?void 0:u.slice())||[]});a&&g.run.push(a),c&&(g.preventDefault=!0),h&&(g.stopPropagation=!0)};for(let o of i){let l=o.scope?o.scope.split(" "):["editor"];if(o.any)for(let c of l){let h=t[c]||(t[c]=Object.create(null));h._any||(h._any={preventDefault:!1,stopPropagation:!1,run:[]});let{any:f}=o;for(let u in h)h[u].run.push(d=>f(d,Eu))}let a=o[e]||o.key;if(a)for(let c of l)s(c,a,o.run,o.preventDefault,o.stopPropagation),o.shift&&s(c,"Shift-"+a,o.shift,o.preventDefault,o.stopPropagation)}return t}var Eu=null;function T1(i,e,t,n){Eu=e;let r=Gm(e),s=Ze(r,0),o=mt(s)==r.length&&r!=" ",l="",a=!1,c=!1,h=!1;Ii&&Ii.view==t&&Ii.scope==n&&(l=Ii.prefix+" ",m1.indexOf(e.keyCode)<0&&(c=!0,Ii=null));let f=new Set,u=g=>{if(g){for(let b of g.run)if(!f.has(b)&&(f.add(b),b(t)))return g.stopPropagation&&(h=!0),!0;g.preventDefault&&(g.stopPropagation&&(h=!0),c=!0)}return!1},d=i[n],p,O;return d&&(u(d[l+Qa(r,e,!o)])?a=!0:o&&(e.altKey||e.metaKey||e.ctrlKey)&&!(_.windows&&e.ctrlKey&&e.altKey)&&!(_.mac&&e.altKey&&!(e.ctrlKey||e.metaKey))&&(p=Si[e.keyCode])&&p!=r?(u(d[l+Qa(p,e,!0)])||e.shiftKey&&(O=Br[e.keyCode])!=r&&O!=p&&u(d[l+Qa(O,e,!1)]))&&(a=!0):o&&e.shiftKey&&u(d[l+Qa(r,e,!0)])&&(a=!0),!a&&u(d._any)&&(a=!0)),c&&(a=!0),a&&h&&e.stopPropagation(),Eu=null,a}var jn=class i{constructor(e,t,n,r,s){this.className=e,this.left=t,this.top=n,this.width=r,this.height=s}draw(){let e=document.createElement("div");return e.className=this.className,this.adjust(e),e}update(e,t){return t.className!=this.className?!1:(this.adjust(e),!0)}adjust(e){e.style.left=this.left+"px",e.style.top=this.top+"px",this.width!=null&&(e.style.width=this.width+"px"),e.style.height=this.height+"px"}eq(e){return this.left==e.left&&this.top==e.top&&this.width==e.width&&this.height==e.height&&this.className==e.className}static forRange(e,t,n){if(n.empty){let r=e.coordsAtPos(n.head,n.assoc||1);if(!r)return[];let s=A1(e);return[new i(t,r.left-s.left,r.top-s.top,null,r.bottom-r.top)]}else return iP(e,t,n)}};function A1(i){let e=i.scrollDOM.getBoundingClientRect();return{left:(i.textDirection==re.LTR?e.left:e.right-i.scrollDOM.clientWidth*i.scaleX)-i.scrollDOM.scrollLeft*i.scaleX,top:e.top-i.scrollDOM.scrollTop*i.scaleY}}function Q0(i,e,t,n){let r=i.coordsAtPos(e,t*2);if(!r)return n;let s=i.dom.getBoundingClientRect(),o=(r.top+r.bottom)/2,l=i.posAtCoords({x:s.left+1,y:o}),a=i.posAtCoords({x:s.right-1,y:o});return l==null||a==null?n:{from:Math.max(n.from,Math.min(l,a)),to:Math.min(n.to,Math.max(l,a))}}function iP(i,e,t){if(t.to<=i.viewport.from||t.from>=i.viewport.to)return[];let n=Math.max(t.from,i.viewport.from),r=Math.min(t.to,i.viewport.to),s=i.textDirection==re.LTR,o=i.contentDOM,l=o.getBoundingClientRect(),a=A1(i),c=o.querySelector(".cm-line"),h=c&&window.getComputedStyle(c),f=l.left+(h?parseInt(h.paddingLeft)+Math.min(0,parseInt(h.textIndent)):0),u=l.right-(h?parseInt(h.paddingRight):0),d=pu(i,n,1),p=pu(i,r,-1),O=d.type==Le.Text?d:null,g=p.type==Le.Text?p:null;if(O&&(i.lineWrapping||d.widgetLineBreaks)&&(O=Q0(i,n,1,O)),g&&(i.lineWrapping||p.widgetLineBreaks)&&(g=Q0(i,r,-1,g)),O&&g&&O.from==g.from&&O.to==g.to)return w(x(t.from,t.to,O));{let C=O?x(t.from,null,O):X(d,!1),T=g?x(null,t.to,g):X(p,!0),y=[];return(O||d).to<(g||p).from-(O&&g?1:0)||d.widgetLineBreaks>1&&C.bottom+i.defaultLineHeight/2<T.top?y.push(b(f,C.bottom,u,T.top)):C.bottom<T.top&&i.elementAtHeight((C.bottom+T.top)/2).type==Le.Text&&(C.bottom=T.top=(C.bottom+T.top)/2),w(C).concat(y).concat(w(T))}function b(C,T,y,Q){return new jn(e,C-a.left,T-a.top,Math.max(0,y-C),Q-T)}function w({top:C,bottom:T,horizontal:y}){let Q=[];for(let S=0;S<y.length;S+=2)Q.push(b(y[S],C,y[S+1],T));return Q}function x(C,T,y){let Q=1e9,S=-1e9,$=[];function k(D,V,N,j,H){let ie=i.coordsAtPos(D,D==y.to?-2:2),je=i.coordsAtPos(N,N==y.from?2:-2);!ie||!je||(Q=Math.min(ie.top,je.top,Q),S=Math.max(ie.bottom,je.bottom,S),H==re.LTR?$.push(s&&V?f:ie.left,s&&j?u:je.right):$.push(!s&&j?f:je.left,!s&&V?u:ie.right))}let P=C??y.from,E=T??y.to;for(let D of i.visibleRanges)if(D.to>P&&D.from<E)for(let V=Math.max(D.from,P),N=Math.min(D.to,E);;){let j=i.state.doc.lineAt(V);for(let H of i.bidiSpans(j)){let ie=H.from+j.from,je=H.to+j.from;if(ie>=N)break;je>V&&k(Math.max(ie,V),C==null&&ie<=P,Math.min(je,N),T==null&&je>=E,H.dir)}if(V=j.to+1,V>=N)break}return $.length==0&&k(P,C==null,E,T==null,i.textDirection),{top:Q,bottom:S,horizontal:$}}function X(C,T){let y=l.top+(T?C.top:C.bottom);return{top:y,bottom:y,horizontal:[]}}}function nP(i,e){return i.constructor==e.constructor&&i.eq(e)}var Zu=class{constructor(e,t){this.view=e,this.layer=t,this.drawn=[],this.scaleX=1,this.scaleY=1,this.measureReq={read:this.measure.bind(this),write:this.draw.bind(this)},this.dom=e.scrollDOM.appendChild(document.createElement("div")),this.dom.classList.add("cm-layer"),t.above&&this.dom.classList.add("cm-layer-above"),t.class&&this.dom.classList.add(t.class),this.scale(),this.dom.setAttribute("aria-hidden","true"),this.setOrder(e.state),e.requestMeasure(this.measureReq),t.mount&&t.mount(this.dom,e)}update(e){e.startState.facet(Ta)!=e.state.facet(Ta)&&this.setOrder(e.state),(this.layer.update(e,this.dom)||e.geometryChanged)&&(this.scale(),e.view.requestMeasure(this.measureReq))}docViewUpdate(e){this.layer.updateOnDocViewUpdate!==!1&&e.requestMeasure(this.measureReq)}setOrder(e){let t=0,n=e.facet(Ta);for(;t<n.length&&n[t]!=this.layer;)t++;this.dom.style.zIndex=String((this.layer.above?150:-1)-t)}measure(){return this.layer.markers(this.view)}scale(){let{scaleX:e,scaleY:t}=this.view;(e!=this.scaleX||t!=this.scaleY)&&(this.scaleX=e,this.scaleY=t,this.dom.style.transform=`scale(${1/e}, ${1/t})`)}draw(e){if(e.length!=this.drawn.length||e.some((t,n)=>!nP(t,this.drawn[n]))){let t=this.dom.firstChild,n=0;for(let r of e)r.update&&t&&r.constructor&&this.drawn[n].constructor&&r.update(t,this.drawn[n])?(t=t.nextSibling,n++):this.dom.insertBefore(r.draw(),t);for(;t;){let r=t.nextSibling;t.remove(),t=r}this.drawn=e,_.webkit&&(this.dom.style.display=this.dom.firstChild?"":"none")}}destroy(){this.layer.destroy&&this.layer.destroy(this.dom,this.view),this.dom.remove()}},Ta=R.define();function M1(i){return[se.define(e=>new Zu(e,i)),Ta.of(i)]}var ts=R.define({combine(i){return Ye(i,{cursorBlinkRate:1200,drawRangeCursor:!0,iosSelectionHandles:!0},{cursorBlinkRate:(e,t)=>Math.min(e,t),drawRangeCursor:(e,t)=>e||t})}});function R1(i={}){return[ts.of(i),rP,sP,lP,n1.of(!0)]}function X1(i){return i.startState.facet(ts)!=i.state.facet(ts)}var rP=M1({above:!0,markers(i){let{state:e}=i,t=e.facet(ts),n=[];for(let r of e.selection.ranges){let s=r==e.selection.main;if(r.empty||t.drawRangeCursor&&!(s&&_.ios&&t.iosSelectionHandles)){let o=s?"cm-cursor cm-cursor-primary":"cm-cursor cm-cursor-secondary",l=r.empty?r:v.cursor(r.head,r.assoc);for(let a of jn.forRange(i,o,l))n.push(a)}}return n},update(i,e){i.transactions.some(n=>n.selection)&&(e.style.animationName=e.style.animationName=="cm-blink"?"cm-blink2":"cm-blink");let t=X1(i);return t&&v0(i.state,e),i.docChanged||i.selectionSet||t},mount(i,e){v0(e.state,i)},class:"cm-cursorLayer"});function v0(i,e){e.style.animationDuration=i.facet(ts).cursorBlinkRate+"ms"}var sP=M1({above:!1,markers(i){let e=[],{main:t,ranges:n}=i.state.selection;for(let r of n)if(!r.empty)for(let s of jn.forRange(i,"cm-selectionBackground",r))e.push(s);if(_.ios&&!t.empty&&i.state.facet(ts).iosSelectionHandles){for(let r of jn.forRange(i,"cm-selectionHandle cm-selectionHandle-start",v.cursor(t.from,1)))e.push(r);for(let r of jn.forRange(i,"cm-selectionHandle cm-selectionHandle-end",v.cursor(t.to,1)))e.push(r)}return e},update(i,e){return i.docChanged||i.selectionSet||i.viewportChanged||X1(i)},class:"cm-selectionLayer"}),oP=_.gecko&&_.gecko_version==153?"#ffffff01":"transparent",lP=at.highest(M.theme({".cm-line":{"& ::selection, &::selection":{backgroundColor:`${oP} !important`},caretColor:"transparent !important"},".cm-content":{caretColor:"transparent !important","& :focus":{caretColor:"initial !important","&::selection, & ::selection":{backgroundColor:"Highlight !important"}}}})),E1=Y.define({map(i,e){return i==null?null:e.mapPos(i)}}),so=de.define({create(){return null},update(i,e){return i!=null&&(i=e.changes.mapPos(i)),e.effects.reduce((t,n)=>n.is(E1)?n.value:t,i)}}),aP=se.fromClass(class{constructor(i){this.view=i,this.cursor=null,this.measureReq={read:this.readPos.bind(this),write:this.drawCursor.bind(this)}}update(i){var e;let t=i.state.field(so);t==null?this.cursor!=null&&((e=this.cursor)===null||e===void 0||e.remove(),this.cursor=null):(this.cursor||(this.cursor=this.view.scrollDOM.appendChild(document.createElement("div")),this.cursor.className="cm-dropCursor"),(i.startState.field(so)!=t||i.docChanged||i.geometryChanged)&&this.view.requestMeasure(this.measureReq))}readPos(){let{view:i}=this,e=i.state.field(so),t=e!=null&&i.coordsAtPos(e);if(!t)return null;let n=i.scrollDOM.getBoundingClientRect();return{left:t.left-n.left+i.scrollDOM.scrollLeft*i.scaleX,top:t.top-n.top+i.scrollDOM.scrollTop*i.scaleY,height:t.bottom-t.top}}drawCursor(i){if(this.cursor){let{scaleX:e,scaleY:t}=this.view;i?(this.cursor.style.left=i.left/e+"px",this.cursor.style.top=i.top/t+"px",this.cursor.style.height=i.height/t+"px"):this.cursor.style.left="-100000px"}}destroy(){this.cursor&&this.cursor.remove()}setDropPos(i){this.view.state.field(so)!=i&&this.view.dispatch({effects:E1.of(i)})}},{eventObservers:{dragover(i){this.setDropPos(this.view.posAtCoords({x:i.clientX,y:i.clientY}))},dragleave(i){(i.target==this.view.contentDOM||!this.view.contentDOM.contains(i.relatedTarget))&&this.setDropPos(null)},dragend(){this.setDropPos(null)},drop(){this.setDropPos(null)}}});function Z1(){return[so,aP]}function $0(i,e,t,n,r){e.lastIndex=0;for(let s=i.iterRange(t,n),o=t,l;!s.next().done;o+=s.value.length)if(!s.lineBreak)for(;l=e.exec(s.value);)r(o+l.index,l)}function cP(i,e){let t=i.visibleRanges;if(t.length==1&&t[0].from==i.viewport.from&&t[0].to==i.viewport.to)return t;let n=[];for(let{from:r,to:s}of t)r=Math.max(i.state.doc.lineAt(r).from,r-e),s=Math.min(i.state.doc.lineAt(s).to,s+e),n.length&&n[n.length-1].to>=r?n[n.length-1].to=s:n.push({from:r,to:s});return n}var _u=class{constructor(e){let{regexp:t,decoration:n,decorate:r,boundary:s,maxLength:o=1e3}=e;if(!t.global)throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");if(this.regexp=t,r)this.addMatch=(l,a,c,h)=>r(h,c,c+l[0].length,l,a);else if(typeof n=="function")this.addMatch=(l,a,c,h)=>{let f=n(l,a,c);f&&h(c,c+l[0].length,f)};else if(n)this.addMatch=(l,a,c,h)=>h(c,c+l[0].length,n);else throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");this.boundary=s,this.maxLength=o}createDeco(e){let t=new lt,n=t.add.bind(t);for(let{from:r,to:s}of cP(e,this.maxLength))$0(e.state.doc,this.regexp,r,s,(o,l)=>this.addMatch(l,e,o,n));return t.finish()}updateDeco(e,t){let n=1e9,r=-1;return e.docChanged&&e.changes.iterChanges((s,o,l,a)=>{a>=e.view.viewport.from&&l<=e.view.viewport.to&&(n=Math.min(l,n),r=Math.max(a,r))}),e.viewportMoved||r-n>1e3?this.createDeco(e.view):r>-1?this.updateRange(e.view,t.map(e.changes),n,r):t}updateRange(e,t,n,r){for(let s of e.visibleRanges){let o=Math.max(s.from,n),l=Math.min(s.to,r);if(l>=o){let a=e.state.doc.lineAt(o),c=a.to<l?e.state.doc.lineAt(l):a,h=Math.max(s.from,a.from),f=Math.min(s.to,c.to);if(this.boundary){for(;o>a.from;o--)if(this.boundary.test(a.text[o-1-a.from])){h=o;break}for(;l<c.to;l++)if(this.boundary.test(c.text[l-c.from])){f=l;break}}let u=[],d,p=(O,g,b)=>u.push(b.range(O,g));if(a==c)for(this.regexp.lastIndex=h-a.from;(d=this.regexp.exec(a.text))&&d.index<f-a.from;)this.addMatch(d,e,d.index+a.from,p);else $0(e.state.doc,this.regexp,h,f,(O,g)=>this.addMatch(g,e,O,p));t=t.update({filterFrom:h,filterTo:f,filter:(O,g)=>O<h||g>f,add:u})}}return t}},Lu=/x/.unicode!=null?"gu":"g",hP=new RegExp(`[\0-\b +-\x7F-\x9F\xAD\u061C\u200B\u200E\u200F\u2028\u2029\u202D\u202E\u2066\u2067\u2069\uFEFF\uFFF9-\uFFFC]`,Lu),fP={0:"null",7:"bell",8:"backspace",10:"newline",11:"vertical tab",13:"carriage return",27:"escape",8203:"zero width space",8204:"zero width non-joiner",8205:"zero width joiner",8206:"left-to-right mark",8207:"right-to-left mark",8232:"line separator",8237:"left-to-right override",8238:"right-to-left override",8294:"left-to-right isolate",8295:"right-to-left isolate",8297:"pop directional isolate",8233:"paragraph separator",65279:"zero width no-break space",65532:"object replacement"},Nf=null;function uP(){var i;if(Nf==null&&typeof document<"u"&&document.body){let e=document.body.style;Nf=((i=e.tabSize)!==null&&i!==void 0?i:e.MozTabSize)!=null}return Nf||!1}var Aa=R.define({combine(i){let e=Ye(i,{render:null,specialChars:hP,addSpecialChars:null});return(e.replaceTabs=!uP())&&(e.specialChars=new RegExp(" |"+e.specialChars.source,Lu)),e.addSpecialChars&&(e.specialChars=new RegExp(e.specialChars.source+"|"+e.addSpecialChars.source,Lu)),e}});function _1(i={}){return[Aa.of(i),dP()]}var P0=null;function dP(){return P0||(P0=se.fromClass(class{constructor(i){this.view=i,this.decorations=L.none,this.decorationCache=Object.create(null),this.decorator=this.makeDecorator(i.state.facet(Aa)),this.decorations=this.decorator.createDeco(i)}makeDecorator(i){return new _u({regexp:i.specialChars,decoration:(e,t,n)=>{let{doc:r}=t.state,s=Ze(e[0],0);if(s==9){let o=r.lineAt(n),l=t.state.tabSize,a=Ke(o.text,l,n-o.from);return L.replace({widget:new ju((l-a%l)*this.view.defaultCharacterWidth/this.view.scaleX)})}return this.decorationCache[s]||(this.decorationCache[s]=L.replace({widget:new Du(i,s)}))},boundary:i.replaceTabs?void 0:/[^]/})}update(i){let e=i.state.facet(Aa);i.startState.facet(Aa)!=e?(this.decorator=this.makeDecorator(e),this.decorations=this.decorator.createDeco(i.view)):this.decorations=this.decorator.updateDeco(i,this.decorations)}},{decorations:i=>i.decorations}))}var pP="\u2022";function OP(i){return i>=32?pP:i==10?"\u2424":String.fromCharCode(9216+i)}var Du=class extends De{constructor(e,t){super(),this.options=e,this.code=t}eq(e){return e.code==this.code}toDOM(e){let t=OP(this.code),n=e.state.phrase("Control character")+" "+(fP[this.code]||"0x"+this.code.toString(16)),r=this.options.render&&this.options.render(this.code,n,t);if(r)return r;let s=document.createElement("span");return s.textContent=t,s.title=n,s.setAttribute("aria-label",n),s.className="cm-specialChar",s}ignoreEvent(){return!1}},ju=class extends De{constructor(e){super(),this.width=e}eq(e){return e.width==this.width}toDOM(){let e=document.createElement("span");return e.textContent=" ",e.className="cm-tab",e.style.width=this.width+"px",e}ignoreEvent(){return!1}};function L1(){return mP}var gP=L.line({class:"cm-activeLine"}),mP=se.fromClass(class{constructor(i){this.decorations=this.getDeco(i)}update(i){(i.docChanged||i.selectionSet)&&(this.decorations=this.getDeco(i.view))}getDeco(i){let e=-1,t=[];for(let n of i.state.selection.ranges){let r=i.lineBlockAt(n.head);r.from>e&&(t.push(gP.range(r.from)),e=r.from)}return L.set(t)}},{decorations:i=>i.decorations});var Iu=2e3;function bP(i,e,t){let n=Math.min(e.line,t.line),r=Math.max(e.line,t.line),s=[];if(e.off>Iu||t.off>Iu||e.col<0||t.col<0){let o=Math.min(e.off,t.off),l=Math.max(e.off,t.off);for(let a=n;a<=r;a++){let c=i.doc.line(a);c.length<=l&&s.push(v.range(c.from+o,c.to+l))}}else{let o=Math.min(e.col,t.col),l=Math.max(e.col,t.col);for(let a=n;a<=r;a++){let c=i.doc.line(a),h=ga(c.text,o,i.tabSize,!0);if(h<0)s.push(v.cursor(c.to));else{let f=ga(c.text,l,i.tabSize);s.push(v.range(c.from+h,c.from+f))}}}return s}function yP(i,e){let t=i.coordsAtPos(i.viewport.from);return t?Math.round(Math.abs((t.left-e)/i.defaultCharacterWidth)):-1}function C0(i,e){let t=i.posAtCoords({x:e.clientX,y:e.clientY},!1),n=i.state.doc.lineAt(t),r=t-n.from,s=r>Iu?-1:r==n.length?yP(i,e.clientX):Ke(n.text,i.state.tabSize,t-n.from);return{line:n.number,col:s,off:r}}function SP(i,e){let t=C0(i,e),n=i.state.selection;return t?{update(r){if(r.docChanged){let s=r.changes.mapPos(r.startState.doc.line(t.line).from),o=r.state.doc.lineAt(s);t={line:o.number,col:t.col,off:Math.min(t.off,o.length)},n=n.map(r.changes)}},get(r,s,o){let l=C0(i,r);if(!l)return n;let a=bP(i.state,t,l);return a.length?o?v.create(a.concat(n.ranges)):v.create(a):n}}:null}function D1(i){let e=i?.eventFilter||(t=>t.altKey&&t.button==0);return M.mouseSelectionStyle.of((t,n)=>e(n)?SP(t,n):null)}var xP={Alt:[18,i=>!!i.altKey],Control:[17,i=>!!i.ctrlKey],Shift:[16,i=>!!i.shiftKey],Meta:[91,i=>!!i.metaKey]},wP={style:"cursor: crosshair"};function j1(i={}){let[e,t]=xP[i.key||"Alt"],n=se.fromClass(class{constructor(r){this.view=r,this.isDown=!1}set(r){this.isDown!=r&&(this.isDown=r,this.view.update([]))}},{eventObservers:{keydown(r){this.set(r.keyCode==e||t(r))},keyup(r){(r.keyCode==e||!t(r))&&this.set(!1)},mousemove(r){this.set(t(r))}}});return[n,M.contentAttributes.of(r=>{var s;return!((s=r.plugin(n))===null||s===void 0)&&s.isDown?wP:null})]}var va="-10000px",Wa=class{constructor(e,t,n,r){this.facet=t,this.createTooltipView=n,this.removeTooltipView=r,this.input=e.state.facet(t),this.tooltips=this.input.filter(o=>o);let s=null;this.tooltipViews=this.tooltips.map(o=>s=n(o,s))}update(e,t){var n;let r=e.state.facet(this.facet),s=r.filter(a=>a);if(r===this.input){for(let a of this.tooltipViews)a.update&&a.update(e);return!1}let o=[],l=t?[]:null;for(let a=0;a<s.length;a++){let c=s[a],h=-1;if(c){for(let f=0;f<this.tooltips.length;f++){let u=this.tooltips[f];u&&u.create==c.create&&(h=f)}if(h<0)o[a]=this.createTooltipView(c,a?o[a-1]:null),l&&(l[a]=!!c.above);else{let f=o[a]=this.tooltipViews[h];l&&(l[a]=t[h]),f.update&&f.update(e)}}}for(let a of this.tooltipViews)o.indexOf(a)<0&&(this.removeTooltipView(a),(n=a.destroy)===null||n===void 0||n.call(a));return t&&(l.forEach((a,c)=>t[c]=a),t.length=l.length),this.input=r,this.tooltips=s,this.tooltipViews=o,!0}};function kP(i){let e=i.dom.ownerDocument.documentElement;return{top:0,left:0,bottom:e.clientHeight,right:e.clientWidth}}var qf=R.define({combine:i=>{var e,t,n;return{position:_.ios?"absolute":((e=i.find(r=>r.position))===null||e===void 0?void 0:e.position)||"fixed",parent:((t=i.find(r=>r.parent))===null||t===void 0?void 0:t.parent)||null,tooltipSpace:((n=i.find(r=>r.tooltipSpace))===null||n===void 0?void 0:n.tooltipSpace)||kP}}}),T0=new WeakMap,ed=se.fromClass(class{constructor(i){this.view=i,this.above=[],this.inView=!0,this.madeAbsolute=!1,this.lastTransaction=0,this.measureTimeout=-1;let e=i.state.facet(qf);this.position=e.position,this.parent=e.parent,this.classes=i.themeClasses,this.createContainer(),this.measureReq={read:this.readMeasure.bind(this),write:this.writeMeasure.bind(this),key:this},this.resizeObserver=typeof ResizeObserver=="function"?new ResizeObserver(()=>this.measureSoon()):null,this.manager=new Wa(i,xo,(t,n)=>this.createTooltip(t,n),t=>{this.resizeObserver&&this.resizeObserver.unobserve(t.dom),t.dom.remove()}),this.above=this.manager.tooltips.map(t=>!!t.above),this.intersectionObserver=typeof IntersectionObserver=="function"?new IntersectionObserver(t=>{Date.now()>this.lastTransaction-50&&t.length>0&&t[t.length-1].intersectionRatio<1&&this.measureSoon()},{threshold:[1]}):null,this.observeIntersection(),i.win.addEventListener("resize",this.measureSoon=this.measureSoon.bind(this)),this.maybeMeasure()}createContainer(){this.parent?(this.container=document.createElement("div"),this.container.style.position="relative",this.container.className=this.view.themeClasses,this.parent.appendChild(this.container)):this.container=this.view.dom}observeIntersection(){if(this.intersectionObserver){this.intersectionObserver.disconnect();for(let i of this.manager.tooltipViews)this.intersectionObserver.observe(i.dom)}}measureSoon(){this.measureTimeout<0&&(this.measureTimeout=setTimeout(()=>{this.measureTimeout=-1,this.maybeMeasure()},50))}update(i){i.transactions.length&&(this.lastTransaction=Date.now());let e=this.manager.update(i,this.above);e&&this.observeIntersection();let t=e||i.geometryChanged,n=i.state.facet(qf);if(n.position!=this.position&&!this.madeAbsolute){this.position=n.position;for(let r of this.manager.tooltipViews)r.dom.style.position=this.position;t=!0}if(n.parent!=this.parent){this.parent&&this.container.remove(),this.parent=n.parent,this.createContainer();for(let r of this.manager.tooltipViews)this.container.appendChild(r.dom);t=!0}else this.parent&&this.view.themeClasses!=this.classes&&(this.classes=this.container.className=this.view.themeClasses);t&&this.maybeMeasure()}createTooltip(i,e){let t=i.create(this.view),n=e?e.dom:null;if(t.dom.classList.add("cm-tooltip"),i.arrow&&!t.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")){let r=document.createElement("div");r.className="cm-tooltip-arrow",t.dom.appendChild(r)}return t.dom.style.position=this.position,t.dom.style.top=va,t.dom.style.left="0px",this.container.insertBefore(t.dom,n),t.mount&&t.mount(this.view),this.resizeObserver&&this.resizeObserver.observe(t.dom),t}destroy(){var i,e,t;this.view.win.removeEventListener("resize",this.measureSoon);for(let n of this.manager.tooltipViews)n.dom.remove(),(i=n.destroy)===null||i===void 0||i.call(n);this.parent&&this.container.remove(),(e=this.resizeObserver)===null||e===void 0||e.disconnect(),(t=this.intersectionObserver)===null||t===void 0||t.disconnect(),clearTimeout(this.measureTimeout)}readMeasure(){let i=1,e=1,t=!1;if(this.position=="fixed"&&this.manager.tooltipViews.length){let{dom:s}=this.manager.tooltipViews[0];if(_.safari){let o=s.getBoundingClientRect();t=Math.abs(o.top+1e4)>1||Math.abs(o.left)>1}else t=!!s.offsetParent&&s.offsetParent!=this.container.ownerDocument.body}if(t||this.position=="absolute")if(this.parent){let s=this.parent.getBoundingClientRect();s.width&&s.height&&(i=s.width/this.parent.offsetWidth,e=s.height/this.parent.offsetHeight)}else({scaleX:i,scaleY:e}=this.view.viewState);let n=this.view.scrollDOM.getBoundingClientRect(),r=Ku(this.view);return{visible:{left:n.left+r.left,top:n.top+r.top,right:n.right-r.right,bottom:n.bottom-r.bottom},parent:this.parent?this.container.getBoundingClientRect():this.view.dom.getBoundingClientRect(),pos:this.manager.tooltips.map((s,o)=>{let l=this.manager.tooltipViews[o];return l.getCoords?l.getCoords(s.pos):this.view.coordsAtPos(s.pos)}),size:this.manager.tooltipViews.map(({dom:s})=>s.getBoundingClientRect()),space:this.view.state.facet(qf).tooltipSpace(this.view),scaleX:i,scaleY:e,makeAbsolute:t}}writeMeasure(i){var e;if(i.makeAbsolute){this.madeAbsolute=!0,this.position="absolute";for(let l of this.manager.tooltipViews)l.dom.style.position="absolute"}let{visible:t,space:n,scaleX:r,scaleY:s}=i,o=[];for(let l=0;l<this.manager.tooltips.length;l++){let a=this.manager.tooltips[l],c=this.manager.tooltipViews[l],{dom:h}=c,f=i.pos[l],u=i.size[l];if(!f||a.clip!==!1&&(f.bottom<=Math.max(t.top,n.top)||f.top>=Math.min(t.bottom,n.bottom)||f.right<Math.max(t.left,n.left)-.1||f.left>Math.min(t.right,n.right)+.1)){h.style.top=va;continue}let d=a.arrow?c.dom.querySelector(".cm-tooltip-arrow"):null,p=d?7:0,O=u.right-u.left,g=(e=T0.get(c))!==null&&e!==void 0?e:u.bottom-u.top,b=c.offset||vP,w=this.view.textDirection==re.LTR,x=u.width>n.right-n.left?w?n.left:n.right-u.width:w?Math.max(n.left,Math.min(f.left-(d?14:0)+b.x,n.right-O)):Math.min(Math.max(n.left,f.left-O+(d?14:0)-b.x),n.right-O),X=this.above[l];!a.strictSide&&(X?f.top-g-p-b.y<n.top:f.bottom+g+p+b.y>n.bottom)&&X==n.bottom-f.bottom>f.top-n.top&&(X=this.above[l]=!X);let C=(X?f.top-n.top:n.bottom-f.bottom)-p;if(C<g&&c.resize!==!1){if(C<this.view.defaultLineHeight){h.style.top=va;continue}T0.set(c,g),h.style.height=(g=C)/s+"px"}else h.style.height&&(h.style.height="");let T=X?f.top-g-p-b.y:f.bottom+p+b.y,y=x+O;if(c.overlap!==!0)for(let Q of o)Q.left<y&&Q.right>x&&Q.top<T+g&&Q.bottom>T&&(T=X?Q.top-g-2-p:Q.bottom+p+2);if(this.position=="absolute"?(h.style.top=(T-i.parent.top)/s+"px",A0(h,(x-i.parent.left)/r)):(h.style.top=T/s+"px",A0(h,x/r)),d){let Q=f.left+(w?b.x:-b.x)-(x+14-7);d.style.left=Q/r+"px"}c.overlap!==!0&&o.push({left:x,top:T,right:y,bottom:T+g}),h.classList.toggle("cm-tooltip-above",X),h.classList.toggle("cm-tooltip-below",!X),c.positioned&&c.positioned(i.space)}}maybeMeasure(){if(this.manager.tooltips.length&&(this.view.inView&&this.view.requestMeasure(this.measureReq),this.inView!=this.view.inView&&(this.inView=this.view.inView,!this.inView)))for(let i of this.manager.tooltipViews)i.dom.style.top=va}},{eventObservers:{scroll(){this.maybeMeasure()}}});function A0(i,e){let t=parseInt(i.style.left,10);(isNaN(t)||Math.abs(e-t)>1)&&(i.style.left=e+"px")}var QP=M.baseTheme({".cm-tooltip":{zIndex:500,boxSizing:"border-box"},"&light .cm-tooltip":{border:"1px solid #bbb",backgroundColor:"#f5f5f5"},"&light .cm-tooltip-section:not(:first-child)":{borderTop:"1px solid #bbb"},"&dark .cm-tooltip":{backgroundColor:"#333338",color:"white"},".cm-tooltip-arrow":{height:"7px",width:"14px",position:"absolute",zIndex:-1,overflow:"hidden","&:before, &:after":{content:"''",position:"absolute",width:0,height:0,borderLeft:"7px solid transparent",borderRight:"7px solid transparent"},".cm-tooltip-above &":{bottom:"-7px","&:before":{borderTop:"7px solid #bbb"},"&:after":{borderTop:"7px solid #f5f5f5",bottom:"1px"}},".cm-tooltip-below &":{top:"-7px","&:before":{borderBottom:"7px solid #bbb"},"&:after":{borderBottom:"7px solid #f5f5f5",top:"1px"}}},"&dark .cm-tooltip .cm-tooltip-arrow":{"&:before":{borderTopColor:"#333338",borderBottomColor:"#333338"},"&:after":{borderTopColor:"transparent",borderBottomColor:"transparent"}}}),vP={x:0,y:0},xo=R.define({enables:[ed,QP]}),Na=R.define({combine:i=>i.reduce((e,t)=>e.concat(t),[])}),qa=class i{static create(e){return new i(e)}constructor(e){this.view=e,this.mounted=!1,this.dom=document.createElement("div"),this.dom.classList.add("cm-tooltip-hover"),this.manager=new Wa(e,Na,(t,n)=>this.createHostedView(t,n),t=>t.dom.remove())}createHostedView(e,t){let n=e.create(this.view);return n.dom.classList.add("cm-tooltip-section"),this.dom.insertBefore(n.dom,t?t.dom.nextSibling:this.dom.firstChild),this.mounted&&n.mount&&n.mount(this.view),n}mount(e){for(let t of this.manager.tooltipViews)t.mount&&t.mount(e);this.mounted=!0}positioned(e){for(let t of this.manager.tooltipViews)t.positioned&&t.positioned(e)}update(e){this.manager.update(e)}destroy(){var e;for(let t of this.manager.tooltipViews)(e=t.destroy)===null||e===void 0||e.call(t)}passProp(e){let t;for(let n of this.manager.tooltipViews){let r=n[e];if(r!==void 0){if(t===void 0)t=r;else if(t!==r)return}}return t}get offset(){return this.passProp("offset")}get getCoords(){return this.passProp("getCoords")}get overlap(){return this.passProp("overlap")}get resize(){return this.passProp("resize")}},$P=xo.compute([Na],i=>{let e=i.facet(Na);return e.length===0?null:{pos:Math.min(...e.map(t=>t.pos)),end:Math.max(...e.map(t=>{var n;return(n=t.end)!==null&&n!==void 0?n:t.pos})),create:qa.create,above:e[0].above,arrow:e.some(t=>t.arrow)}}),I1=R.define(),Uu=class{constructor(e,t,n,r,s,o){this.view=e,this.source=t,this.field=n,this.locked=r,this.setHover=s,this.hoverTime=o,this.hoverTimeout=-1,this.restartTimeout=-1,this.pending=null,this.lastMove={x:0,y:0,target:e.dom,time:0},this.checkHover=this.checkHover.bind(this),e.dom.addEventListener("mouseleave",this.mouseleave=this.mouseleave.bind(this)),e.dom.addEventListener("mousemove",this.mousemove=this.mousemove.bind(this))}update(e){this.pending&&(this.pending=null,clearTimeout(this.restartTimeout),this.restartTimeout=setTimeout(()=>this.startHover(),20))}get active(){return this.view.state.field(this.field)}checkHover(){if(this.hoverTimeout=-1,this.active.length)return;let e=Date.now()-this.lastMove.time;e<this.hoverTime?this.hoverTimeout=setTimeout(this.checkHover,this.hoverTime-e):this.startHover()}startHover(){clearTimeout(this.restartTimeout);let{view:e,lastMove:t}=this,n=e.docView.tile.nearest(t.target);if(!n)return;let r,s=1;if(n.isWidget())r=n.posAtStart;else{if(r=e.posAtCoords(t),r==null)return;let o=e.coordsAtPos(r);if(!o||t.y<o.top||t.y>o.bottom||t.x<o.left-e.defaultCharacterWidth||t.x>o.right+e.defaultCharacterWidth)return;let l=e.bidiSpans(e.state.doc.lineAt(r)).find(c=>c.from<=r&&c.to>=r),a=l&&l.dir==re.RTL?-1:1;s=t.x<o.left?-a:a}this.activateHover(e,r,s)}activateHover(e,t,n,r){let s=this.source(e,t,n),o=l=>{if(l&&!(Array.isArray(l)&&!l.length)){let a=Array.isArray(l)?l:[l];r&&this.locked.set(a,r),e.dispatch({effects:this.setHover.of(a)})}};if(s&&"then"in s){let l=this.pending={pos:t};s.then(a=>{this.pending==l&&(this.pending=null,o(a))},a=>_e(e.state,a,"hover tooltip"))}else o(s)}get tooltip(){let e=this.view.plugin(ed),t=e?e.manager.tooltips.findIndex(n=>n.create==qa.create):-1;return t>-1?e.manager.tooltipViews[t]:null}mousemove(e){var t,n;this.lastMove={x:e.clientX,y:e.clientY,target:e.target,time:Date.now()},this.hoverTimeout<0&&(this.hoverTimeout=setTimeout(this.checkHover,this.hoverTime));let{active:r,tooltip:s}=this;if(r.length&&!this.locked.has(r)&&s&&!PP(s.dom,e)||this.pending){let{pos:o}=r[0]||this.pending,l=(n=(t=r[0])===null||t===void 0?void 0:t.end)!==null&&n!==void 0?n:o;(o==l?this.view.posAtCoords(this.lastMove)!=o:!CP(this.view,o,l,e.clientX,e.clientY))&&(this.view.dispatch({effects:this.setHover.of([])}),this.pending=null)}}mouseleave(e){clearTimeout(this.hoverTimeout),this.hoverTimeout=-1;let{active:t}=this;if(t.length&&!this.locked.has(t)){let{tooltip:n}=this;n&&n.dom.contains(e.relatedTarget)?this.watchTooltipLeave(n.dom):this.view.dispatch({effects:this.setHover.of([])})}}watchTooltipLeave(e){let t=n=>{e.removeEventListener("mouseleave",t);let{active:r}=this;r.length&&!this.locked.has(r)&&!this.view.dom.contains(n.relatedTarget)&&this.view.dispatch({effects:this.setHover.of([])})};e.addEventListener("mouseleave",t)}destroy(){clearTimeout(this.hoverTimeout),clearTimeout(this.restartTimeout),this.view.dom.removeEventListener("mouseleave",this.mouseleave),this.view.dom.removeEventListener("mousemove",this.mousemove)}},$a=4;function PP(i,e){let{left:t,right:n,top:r,bottom:s}=i.getBoundingClientRect(),o;if(o=i.querySelector(".cm-tooltip-arrow")){let l=o.getBoundingClientRect();r=Math.min(l.top,r),s=Math.max(l.bottom,s)}return e.clientX>=t-$a&&e.clientX<=n+$a&&e.clientY>=r-$a&&e.clientY<=s+$a}function CP(i,e,t,n,r,s){let o=i.scrollDOM.getBoundingClientRect(),l=i.documentTop+i.documentPadding.top+i.contentHeight;if(o.left>n||o.right<n||o.top>r||Math.min(o.bottom,l)<r)return!1;let a=i.posAtCoords({x:n,y:r},!1);return a>=e&&a<=t}function U1(i,e={}){let t=Y.define(),n=new WeakMap,r=de.define({create(){return[]},update(o,l){let a=n.get(o);if(o.length&&(e.hideOnChange&&(l.docChanged||l.selection)?o=[]:a&&a(l)?o=[]:e.hideOn&&(o=o.filter(c=>!e.hideOn(l,c)))),l.docChanged&&o.length){let c=[];for(let h of o){let f=l.changes.mapPos(h.pos,-1,$e.TrackDel);if(f!=null){let u=Object.assign(Object.create(null),h);u.pos=f,u.end!=null&&(u.end=l.changes.mapPos(u.end)),c.push(u)}}o=c}for(let c of l.effects)c.is(t)&&(o=c.value,a=void 0),(c.is(TP)&&!c.value||c.value==r)&&(o=[]);return o.length&&a&&n.set(o,a),o},provide:o=>Na.from(o)}),s=se.define(o=>new Uu(o,i,r,n,t,e.hoverTime||300));return{active:r,extension:[r,s,I1.of(s),$P]}}function B1(i,e,t,n={}){var r;let s=i.state.facet(I1).map(o=>i.plugin(o)).filter(o=>!!o);if(n.tooltip&&n.tooltip.active){let o=s.find(l=>l.field==n.tooltip.active);o&&(s=[o])}for(let o of s)o.activateHover(i,e,t,(r=n.until)!==null&&r!==void 0?r:(()=>!1))}function td(i,e){let t=i.plugin(ed);if(!t)return null;let n=t.manager.tooltips.indexOf(e);return n<0?null:t.manager.tooltipViews[n]}var TP=Y.define();var M0=R.define({combine(i){let e,t;for(let n of i)e=e||n.topContainer,t=t||n.bottomContainer;return{topContainer:e,bottomContainer:t}}});function is(i,e){let t=i.plugin(V1),n=t?t.specs.indexOf(e):-1;return n>-1?t.panels[n]:null}var V1=se.fromClass(class{constructor(i){this.input=i.state.facet(Vn),this.specs=this.input.filter(t=>t),this.panels=this.specs.map(t=>t(i));let e=i.state.facet(M0);this.top=new Wr(i,!0,e.topContainer),this.bottom=new Wr(i,!1,e.bottomContainer),this.top.sync(this.panels.filter(t=>t.top)),this.bottom.sync(this.panels.filter(t=>!t.top));for(let t of this.panels)t.dom.classList.add("cm-panel"),t.mount&&t.mount()}update(i){let e=i.state.facet(M0);this.top.container!=e.topContainer&&(this.top.sync([]),this.top=new Wr(i.view,!0,e.topContainer)),this.bottom.container!=e.bottomContainer&&(this.bottom.sync([]),this.bottom=new Wr(i.view,!1,e.bottomContainer)),this.top.syncClasses(),this.bottom.syncClasses();let t=i.state.facet(Vn);if(t!=this.input){let n=t.filter(a=>a),r=[],s=[],o=[],l=[];for(let a of n){let c=this.specs.indexOf(a),h;c<0?(h=a(i.view),l.push(h)):(h=this.panels[c],h.update&&h.update(i)),r.push(h),(h.top?s:o).push(h)}this.specs=n,this.panels=r,this.top.sync(s),this.bottom.sync(o);for(let a of l)a.dom.classList.add("cm-panel"),a.mount&&a.mount()}else for(let n of this.panels)n.update&&n.update(i)}destroy(){this.top.sync([]),this.bottom.sync([])}},{provide:i=>M.scrollMargins.of(e=>{let t=e.plugin(i);return t&&{top:t.top.scrollMargin(),bottom:t.bottom.scrollMargin()}})}),Wr=class{constructor(e,t,n){this.view=e,this.top=t,this.container=n,this.dom=void 0,this.classes="",this.panels=[],this.syncClasses()}sync(e){for(let t of this.panels)t.destroy&&e.indexOf(t)<0&&t.destroy();this.panels=e,this.syncDOM()}syncDOM(){if(this.panels.length==0){this.dom&&(this.dom.remove(),this.dom=void 0);return}if(!this.dom){this.dom=document.createElement("div"),this.dom.className=this.top?"cm-panels cm-panels-top":"cm-panels cm-panels-bottom";let t=this.container||this.view.dom;t.insertBefore(this.dom,this.top?t.firstChild:null)}let e=this.dom.firstChild;for(let t of this.panels)if(t.dom.parentNode==this.dom){for(;e!=t.dom;)e=R0(e);e=e.nextSibling}else this.dom.insertBefore(t.dom,e);for(;e;)e=R0(e)}scrollMargin(){return!this.dom||this.container?0:Math.max(0,this.top?this.dom.getBoundingClientRect().bottom-Math.max(0,this.view.scrollDOM.getBoundingClientRect().top):Math.min(innerHeight,this.view.scrollDOM.getBoundingClientRect().bottom)-this.dom.getBoundingClientRect().top)}syncClasses(){if(!(!this.container||this.classes==this.view.themeClasses)){for(let e of this.classes.split(" "))e&&this.container.classList.remove(e);for(let e of(this.classes=this.view.themeClasses).split(" "))e&&this.container.classList.add(e)}}};function R0(i){let e=i.nextSibling;return i.remove(),e}var Vn=R.define({enables:V1});function Y1(i,e){let t,n=new Promise(o=>t=o),r=o=>AP(o,e,t);i.state.field(Ma,!1)?i.dispatch({effects:W1.of(r)}):i.dispatch({effects:Y.appendConfig.of(Ma.init(()=>[r]))});let s=N1.of(r);return{close:s,result:n.then(o=>((i.win.queueMicrotask||(a=>i.win.setTimeout(a,10)))(()=>{i.state.field(Ma).indexOf(r)>-1&&i.dispatch({effects:s})}),o))}}function z1(i,e){let t=i.state.field(Ma,!1)||[];for(let n of t){let r=is(i,n);if(r&&r.dom.classList.contains(e))return r}return null}var Ma=de.define({create(){return[]},update(i,e){for(let t of e.effects)t.is(W1)?i=[t.value].concat(i):t.is(N1)&&(i=i.filter(n=>n!=t.value));return i},provide:i=>Vn.computeN([i],e=>e.field(i))}),W1=Y.define(),N1=Y.define();function AP(i,e,t){let n=e.content?e.content(i,()=>o(null)):null;if(!n){if(n=te("form"),e.input){let l=te("input",e.input);/^(text|password|number|email|tel|url)$/.test(l.type)&&l.classList.add("cm-textfield"),l.name||(l.name="input"),n.appendChild(te("label",(e.label||"")+": ",l))}else n.appendChild(document.createTextNode(e.label||""));n.appendChild(document.createTextNode(" ")),n.appendChild(te("button",{class:"cm-button",type:"submit"},e.submitLabel||"OK"))}let r=n.nodeName=="FORM"?[n]:n.querySelectorAll("form");for(let l=0;l<r.length;l++){let a=r[l];a.addEventListener("keydown",c=>{c.keyCode==27?(c.preventDefault(),o(null)):c.keyCode==13&&(c.preventDefault(),o(a))}),a.addEventListener("submit",c=>{c.preventDefault(),o(a)})}let s=te("div",n,te("button",{onclick:()=>o(null),"aria-label":i.state.phrase("close"),class:"cm-dialog-close",type:"button"},["\xD7"]));e.class&&(s.className=e.class),s.classList.add("cm-dialog");function o(l){s.contains(s.ownerDocument.activeElement)&&i.focus(),t(l)}return{dom:s,top:e.top,mount:()=>{if(e.focus){let l;typeof e.focus=="string"?l=n.querySelector(e.focus):l=n.querySelector("input")||n.querySelector("button"),l&&"select"in l?l.select():l&&"focus"in l&&l.focus()}}}}var yt=class extends Ct{compare(e){return this==e||this.constructor==e.constructor&&this.eq(e)}eq(e){return!1}destroy(e){}};yt.prototype.elementClass="";yt.prototype.toDOM=void 0;yt.prototype.mapMode=$e.TrackBefore;yt.prototype.startSide=yt.prototype.endSide=-1;yt.prototype.point=!0;var Ra=R.define(),MP=R.define(),RP={class:"",renderEmptyElements:!1,elementStyle:"",markers:()=>G.empty,lineMarker:()=>null,widgetMarker:()=>null,lineMarkerChange:null,initialSpacer:null,updateSpacer:null,domEventHandlers:{},side:"before"},po=R.define();function id(i){return[q1(),po.of({...RP,...i})]}var Bu=R.define({combine:i=>i.some(e=>e)});function q1(i){let e=[XP];return i&&i.fixed===!1&&e.push(Bu.of(!0)),e}var XP=se.fromClass(class{constructor(i){this.view=i,this.domAfter=null,this.prevViewport=i.viewport,this.dom=document.createElement("div"),this.dom.className="cm-gutters cm-gutters-before",this.dom.setAttribute("aria-hidden","true"),this.dom.style.minHeight=this.view.contentHeight/this.view.scaleY+"px",this.gutters=i.state.facet(po).map(e=>new Ga(i,e)),this.fixed=!i.state.facet(Bu);for(let e of this.gutters)e.config.side=="after"?this.getDOMAfter().appendChild(e.dom):this.dom.appendChild(e.dom);this.fixed&&(this.dom.style.position="sticky"),this.syncGutters(!1),i.scrollDOM.insertBefore(this.dom,i.contentDOM)}getDOMAfter(){return this.domAfter||(this.domAfter=document.createElement("div"),this.domAfter.className="cm-gutters cm-gutters-after",this.domAfter.setAttribute("aria-hidden","true"),this.domAfter.style.minHeight=this.view.contentHeight/this.view.scaleY+"px",this.domAfter.style.position=this.fixed?"sticky":"",this.view.scrollDOM.appendChild(this.domAfter)),this.domAfter}update(i){if(this.updateGutters(i)){let e=this.prevViewport,t=i.view.viewport,n=Math.min(e.to,t.to)-Math.max(e.from,t.from);this.syncGutters(n<(t.to-t.from)*.8)}if(i.geometryChanged){let e=this.view.contentHeight/this.view.scaleY+"px";this.dom.style.minHeight=e,this.domAfter&&(this.domAfter.style.minHeight=e)}this.view.state.facet(Bu)!=!this.fixed&&(this.fixed=!this.fixed,this.dom.style.position=this.fixed?"sticky":"",this.domAfter&&(this.domAfter.style.position=this.fixed?"sticky":"")),this.prevViewport=i.view.viewport}syncGutters(i){let e=this.dom.nextSibling;i&&(this.dom.remove(),this.domAfter&&this.domAfter.remove());let t=G.iter(this.view.state.facet(Ra),this.view.viewport.from),n=[],r=this.gutters.map(s=>new Yu(s,this.view.viewport,-this.view.documentPadding.top));for(let s of this.view.viewportLineBlocks)if(n.length&&(n=[]),Array.isArray(s.type)){let o=!0;for(let l of s.type)if(l.type==Le.Text&&o){Vu(t,n,l.from);for(let a of r)a.line(this.view,l,n);o=!1}else if(l.widget)for(let a of r)a.widget(this.view,l)}else if(s.type==Le.Text){Vu(t,n,s.from);for(let o of r)o.line(this.view,s,n)}else if(s.widget)for(let o of r)o.widget(this.view,s);for(let s of r)s.finish();i&&(this.view.scrollDOM.insertBefore(this.dom,e),this.domAfter&&this.view.scrollDOM.appendChild(this.domAfter))}updateGutters(i){let e=i.startState.facet(po),t=i.state.facet(po),n=i.docChanged||i.heightChanged||i.viewportChanged||!G.eq(i.startState.facet(Ra),i.state.facet(Ra),i.view.viewport.from,i.view.viewport.to);if(e==t)for(let r of this.gutters)r.update(i)&&(n=!0);else{n=!0;let r=[];for(let s of t){let o=e.indexOf(s);o<0?r.push(new Ga(this.view,s)):(this.gutters[o].update(i),r.push(this.gutters[o]))}for(let s of this.gutters)s.dom.remove(),r.indexOf(s)<0&&s.destroy();for(let s of r)s.config.side=="after"?this.getDOMAfter().appendChild(s.dom):this.dom.appendChild(s.dom);this.gutters=r}return n}destroy(){for(let i of this.gutters)i.destroy();this.dom.remove(),this.domAfter&&this.domAfter.remove()}},{provide:i=>M.scrollMargins.of(e=>{let t=e.plugin(i);if(!t||t.gutters.length==0||!t.fixed)return null;let n=t.dom.offsetWidth*e.scaleX,r=t.domAfter?t.domAfter.offsetWidth*e.scaleX:0;return e.textDirection==re.LTR?{left:n,right:r}:{right:n,left:r}})});function X0(i){return Array.isArray(i)?i:[i]}function Vu(i,e,t){for(;i.value&&i.from<=t;)i.from==t&&e.push(i.value),i.next()}var Yu=class{constructor(e,t,n){this.gutter=e,this.height=n,this.i=0,this.cursor=G.iter(e.markers,t.from)}addElement(e,t,n){let{gutter:r}=this,s=(t.top-this.height)/e.scaleY,o=t.height/e.scaleY;if(this.i==r.elements.length){let l=new Fa(e,o,s,n);r.elements.push(l),r.dom.appendChild(l.dom)}else r.elements[this.i].update(e,o,s,n);this.height=t.bottom,this.i++}line(e,t,n){let r=[];Vu(this.cursor,r,t.from),n.length&&(r=r.concat(n));let s=this.gutter.config.lineMarker(e,t,r);s&&r.unshift(s);let o=this.gutter;r.length==0&&!o.config.renderEmptyElements||this.addElement(e,t,r)}widget(e,t){let n=this.gutter.config.widgetMarker(e,t.widget,t),r=n?[n]:null;for(let s of e.state.facet(MP)){let o=s(e,t.widget,t);o&&(r||(r=[])).push(o)}r&&this.addElement(e,t,r)}finish(){let e=this.gutter;for(;e.elements.length>this.i;){let t=e.elements.pop();e.dom.removeChild(t.dom),t.destroy()}}},Ga=class{constructor(e,t){this.view=e,this.config=t,this.elements=[],this.spacer=null,this.dom=document.createElement("div"),this.dom.className="cm-gutter"+(this.config.class?" "+this.config.class:"");for(let n in t.domEventHandlers)this.dom.addEventListener(n,r=>{let s=r.target,o;if(s!=this.dom&&this.dom.contains(s)){for(;s.parentNode!=this.dom;)s=s.parentNode;let a=s.getBoundingClientRect();o=(a.top+a.bottom)/2}else o=r.clientY;let l=e.lineBlockAtHeight(o-e.documentTop);t.domEventHandlers[n](e,l,r)&&r.preventDefault()});this.markers=X0(t.markers(e)),t.initialSpacer&&(this.spacer=new Fa(e,0,0,[t.initialSpacer(e)]),this.dom.appendChild(this.spacer.dom),this.spacer.dom.style.cssText+="visibility: hidden; pointer-events: none")}update(e){let t=this.markers;if(this.markers=X0(this.config.markers(e.view)),this.spacer&&this.config.updateSpacer){let r=this.config.updateSpacer(this.spacer.markers[0],e);r!=this.spacer.markers[0]&&this.spacer.update(e.view,0,0,[r])}let n=e.view.viewport;return!G.eq(this.markers,t,n.from,n.to)||(this.config.lineMarkerChange?this.config.lineMarkerChange(e):!1)}destroy(){for(let e of this.elements)e.destroy()}},Fa=class{constructor(e,t,n,r){this.height=-1,this.above=0,this.markers=[],this.dom=document.createElement("div"),this.dom.className="cm-gutterElement",this.update(e,t,n,r)}update(e,t,n,r){this.height!=t&&(this.height=t,this.dom.style.height=t+"px"),this.above!=n&&(this.dom.style.marginTop=(this.above=n)?n+"px":""),EP(this.markers,r)||this.setMarkers(e,r)}setMarkers(e,t){let n="cm-gutterElement",r=this.dom.firstChild;for(let s=0,o=0;;){let l=o,a=s<t.length?t[s++]:null,c=!1;if(a){let h=a.elementClass;h&&(n+=" "+h);for(let f=o;f<this.markers.length;f++)if(this.markers[f].compare(a)){l=f,c=!0;break}}else l=this.markers.length;for(;o<l;){let h=this.markers[o++];if(h.toDOM){h.destroy(r);let f=r.nextSibling;r.remove(),r=f}}if(!a)break;a.toDOM&&(c?r=r.nextSibling:this.dom.insertBefore(a.toDOM(e),r)),c&&o++}this.dom.className=n,this.markers=t}destroy(){this.setMarkers(null,[])}};function EP(i,e){if(i.length!=e.length)return!1;for(let t=0;t<i.length;t++)if(!i[t].compare(e[t]))return!1;return!0}var ZP=R.define(),_P=R.define(),Nr=R.define({combine(i){return Ye(i,{formatNumber:String,domEventHandlers:{}},{domEventHandlers(e,t){let n=Object.assign({},e);for(let r in t){let s=n[r],o=t[r];n[r]=s?(l,a,c)=>s(l,a,c)||o(l,a,c):o}return n}})}}),Oo=class extends yt{constructor(e){super(),this.number=e}eq(e){return this.number==e.number}toDOM(){return document.createTextNode(this.number)}};function Gf(i,e){return i.state.facet(Nr).formatNumber(e,i.state)}var LP=po.compute([Nr],i=>({class:"cm-lineNumbers",renderEmptyElements:!1,markers(e){return e.state.facet(ZP)},lineMarker(e,t,n){return n.some(r=>r.toDOM)?null:new Oo(Gf(e,e.state.doc.lineAt(t.from).number))},widgetMarker:(e,t,n)=>{for(let r of e.state.facet(_P)){let s=r(e,t,n);if(s)return s}return null},lineMarkerChange:e=>e.startState.facet(Nr)!=e.state.facet(Nr),initialSpacer(e){return new Oo(Gf(e,E0(e.state.doc.lines)))},updateSpacer(e,t){let n=Gf(t.view,E0(t.view.state.doc.lines));return n==e.number?e:new Oo(n)},domEventHandlers:i.facet(Nr).domEventHandlers,side:"before"}));function G1(i={}){return[Nr.of(i),q1(),LP]}function E0(i){let e=9;for(;e<i;)e=e*10+9;return e}var DP=new class extends yt{constructor(){super(...arguments),this.elementClass="cm-activeLineGutter"}},jP=Ra.compute(["selection"],i=>{let e=[],t=-1;for(let n of i.selection.ranges){let r=i.doc.lineAt(n.head).from;r>t&&(t=r,e.push(DP.range(r)))}return G.of(e)});function F1(){return jP}var tc=class i{constructor(e,t){this.yanchor=e,this.yhead=t}toJSON(){return{yanchor:ea(this.yanchor),yhead:ea(this.yhead)}}static fromJSON(e){return new i(bi(e.yanchor),bi(e.yhead))}};var ic=class{constructor(e,t){this.ytext=e,this.awareness=t}toYPos(e,t=0){return Xr(this.ytext,e,t)}fromYPos(e){let t=Er(bi(e),this.ytext.doc);if(t==null||t.type!==this.ytext)throw new Error("[y-codemirror] The position you want to retrieve was created by a different document");return{pos:t.index,assoc:t.assoc}}toYRange(e){let t=e.assoc,n=this.toYPos(e.anchor,t),r=this.toYPos(e.head,t);return new tc(n,r)}fromYRange(e){let t=this.fromYPos(e.yanchor),n=this.fromYPos(e.yhead);return t.pos===n.pos?v.cursor(n.pos,n.assoc):v.range(t.pos,n.pos)}},Yn=R.define({combine(i){return i[i.length-1]}}),wo=we.define(),nd=class{constructor(e){this.view=e,this.conf=e.state.facet(Yn),this._observer=(t,n)=>{if(n.origin!==this.conf){let r=t.delta,s=[],o=0;for(let l=0;l<r.length;l++){let a=r[l];a.insert!=null?s.push({from:o,to:o,insert:a.insert}):a.delete!=null?(s.push({from:o,to:o+a.delete,insert:""}),o+=a.delete):o+=a.retain}e.dispatch({changes:s,annotations:[wo.of(this.conf)]})}},this._ytext=this.conf.ytext,this._ytext.observe(this._observer)}update(e){if(!e.docChanged||e.transactions.length>0&&e.transactions[0].annotation(wo)===this.conf)return;let t=this.conf.ytext;t.doc.transact(()=>{let n=0;e.changes.iterChanges((r,s,o,l,a)=>{let c=a.sliceString(0,a.length,` +`);r!==s&&t.delete(r+n,s-r),c.length>0&&t.insert(r+n,c),n+=c.length-(s-r)})},this.conf)}destroy(){this._ytext.unobserve(this._observer)}},H1=se.fromClass(nd);var K1=M.baseTheme({".cm-ySelection":{},".cm-yLineSelection":{padding:0,margin:"0px 2px 0px 4px"},".cm-ySelectionCaret":{position:"relative",borderLeft:"1px solid black",borderRight:"1px solid black",marginLeft:"-1px",marginRight:"-1px",boxSizing:"border-box",display:"inline"},".cm-ySelectionCaretDot":{borderRadius:"50%",position:"absolute",width:".4em",height:".4em",top:"-.2em",left:"-.2em",backgroundColor:"inherit",transition:"transform .3s ease-in-out",boxSizing:"border-box"},".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot":{transformOrigin:"bottom center",transform:"scale(0)"},".cm-ySelectionInfo":{position:"absolute",top:"-1.05em",left:"-1px",fontSize:".75em",fontFamily:"serif",fontStyle:"normal",fontWeight:"normal",lineHeight:"normal",userSelect:"none",color:"white",paddingLeft:"2px",paddingRight:"2px",zIndex:101,transition:"opacity .3s ease-in-out",backgroundColor:"inherit",opacity:0,transitionDelay:"0s",whiteSpace:"nowrap"},".cm-ySelectionCaret:hover > .cm-ySelectionInfo":{opacity:1,transitionDelay:"0s"}}),IP=we.define(),rd=class extends De{constructor(e,t){super(),this.color=e,this.name=t}toDOM(){return Es("span",[Ge("class","cm-ySelectionCaret"),Ge("style",`background-color: ${this.color}; border-color: ${this.color}`)],[vr("\u2060"),Es("div",[Ge("class","cm-ySelectionCaretDot")]),vr("\u2060"),Es("div",[Ge("class","cm-ySelectionInfo")],[vr(this.name)]),vr("\u2060")])}eq(e){return e.color===this.color}compare(e){return e.color===this.color}updateDOM(){return!1}get estimatedHeight(){return-1}ignoreEvent(){return!0}},sd=class{constructor(e){this.conf=e.state.facet(Yn),this._listener=({added:t,updated:n,removed:r},s,o)=>{t.concat(n).concat(r).findIndex(a=>a!==this.conf.awareness.doc.clientID)>=0&&e.dispatch({annotations:[IP.of([])]})},this._awareness=this.conf.awareness,this._awareness.on("change",this._listener),this.decorations=G.of([])}destroy(){this._awareness.off("change",this._listener)}update(e){let t=this.conf.ytext,n=t.doc,r=this.conf.awareness,s=[],o=this.conf.awareness.getLocalState();if(o!=null){let l=e.view.hasFocus&&e.view.dom.ownerDocument.hasFocus(),a=l?e.state.selection.main:null,c=o.cursor==null?null:bi(o.cursor.anchor),h=o.cursor==null?null:bi(o.cursor.head);if(a!=null){let f=Xr(t,a.anchor),u=Xr(t,a.head);(o.cursor==null||!ta(c,f)||!ta(h,u))&&r.setLocalStateField("cursor",{anchor:f,head:u})}else o.cursor!=null&&l&&r.setLocalStateField("cursor",null)}r.getStates().forEach((l,a)=>{if(a===r.doc.clientID)return;let c=l.cursor;if(c==null||c.anchor==null||c.head==null)return;let h=Er(c.anchor,n),f=Er(c.head,n);if(h==null||f==null||h.type!==t||f.type!==t)return;let{color:u="#30bced",name:d="Anonymous"}=l.user||{},p=l.user&&l.user.colorLight||u+"33",O=or(h.index,f.index),g=Lt(h.index,f.index),b=e.view.state.doc.lineAt(O),w=e.view.state.doc.lineAt(g);if(b.number===w.number)s.push({from:O,to:g,value:L.mark({attributes:{style:`background-color: ${p}`},class:"cm-ySelection"})});else{s.push({from:O,to:b.from+b.length,value:L.mark({attributes:{style:`background-color: ${p}`},class:"cm-ySelection"})}),s.push({from:w.from,to:g,value:L.mark({attributes:{style:`background-color: ${p}`},class:"cm-ySelection"})});for(let x=b.number+1;x<w.number;x++){let X=e.view.state.doc.line(x).from;s.push({from:X,to:X,value:L.line({attributes:{style:`background-color: ${p}`,class:"cm-yLineSelection"}})})}}s.push({from:f.index,to:f.index,value:L.widget({side:f.index-h.index>0?-1:1,block:!1,widget:new rd(u,d)})})}),this.decorations=L.set(s,!0)}},J1=se.fromClass(sd,{decorations:i=>i.decorations});var nc=class{constructor(e){this.undoManager=e}addTrackedOrigin(e){this.undoManager.addTrackedOrigin(e)}removeTrackedOrigin(e){this.undoManager.removeTrackedOrigin(e)}undo(){return this.undoManager.undo()!=null}redo(){return this.undoManager.redo()!=null}},ko=R.define({combine(i){return i[i.length-1]}}),CX=we.define(),od=class{constructor(e){this.view=e,this.conf=e.state.facet(ko),this._undoManager=this.conf.undoManager,this.syncConf=e.state.facet(Yn),this._beforeChangeSelection=null,this._onStackItemAdded=({stackItem:t,changedParentTypes:n})=>{n.has(this.syncConf.ytext)&&this._beforeChangeSelection&&!t.meta.has(this)&&t.meta.set(this,this._beforeChangeSelection)},this._onStackItemPopped=({stackItem:t})=>{let n=t.meta.get(this);if(n){let r=this.syncConf.fromYRange(n);e.dispatch(e.state.update({selection:r,effects:[M.scrollIntoView(r)]})),this._storeSelection()}},this._storeSelection=()=>{this._beforeChangeSelection=this.syncConf.toYRange(this.view.state.selection.main)},this._undoManager.on("stack-item-added",this._onStackItemAdded),this._undoManager.on("stack-item-popped",this._onStackItemPopped),this._undoManager.addTrackedOrigin(this.syncConf)}update(e){e.selectionSet&&(e.transactions.length===0||e.transactions[0].annotation(wo)!==this.syncConf)&&this._storeSelection()}destroy(){this._undoManager.off("stack-item-added",this._onStackItemAdded),this._undoManager.off("stack-item-popped",this._onStackItemPopped),this._undoManager.removeTrackedOrigin(this.syncConf)}},eb=se.fromClass(od),ld=({state:i,dispatch:e})=>i.facet(ko).undo()||!0,rc=({state:i,dispatch:e})=>i.facet(ko).redo()||!0;var tb=[{key:"Mod-z",run:ld,preventDefault:!0},{key:"Mod-y",mac:"Mod-Shift-z",run:rc,preventDefault:!0},{key:"Mod-Shift-z",run:rc,preventDefault:!0}];var UP=(i,e,{undoManager:t=new Bs(i)}={})=>{let n=new ic(i,e),r=[Yn.of(n),H1];return e&&r.push(K1,J1),t!==!1&&r.push(ko.of(new nc(t)),eb,M.domEventHandlers({beforeinput(s,o){return s.inputType==="historyUndo"?ld(o):s.inputType==="historyRedo"?rc(o):!1}})),r};var BP=0,ht=class{constructor(e,t){this.from=e,this.to=t}},U=class{constructor(e={}){this.id=BP++,this.perNode=!!e.perNode,this.deserialize=e.deserialize||(()=>{throw new Error("This node type doesn't define a deserialize function")}),this.combine=e.combine||null}add(e){if(this.perNode)throw new RangeError("Can't add per-node props to node types");return typeof e!="function"&&(e=Oe.match(e)),t=>{let n=e(t);return n===void 0?null:[this,n]}}};U.closedBy=new U({deserialize:i=>i.split(" ")});U.openedBy=new U({deserialize:i=>i.split(" ")});U.group=new U({deserialize:i=>i.split(" ")});U.isolate=new U({deserialize:i=>{if(i&&i!="rtl"&&i!="ltr"&&i!="auto")throw new RangeError("Invalid value for isolate: "+i);return i||"auto"}});U.contextHash=new U({perNode:!0});U.lookAhead=new U({perNode:!0});U.mounted=new U({perNode:!0});var Yi=class{constructor(e,t,n,r=!1){this.tree=e,this.overlay=t,this.parser=n,this.bracketed=r}static get(e){return e&&e.props&&e.props[U.mounted.id]}},VP=Object.create(null),Oe=class i{constructor(e,t,n,r=0){this.name=e,this.props=t,this.id=n,this.flags=r}static define(e){let t=e.props&&e.props.length?Object.create(null):VP,n=(e.top?1:0)|(e.skipped?2:0)|(e.error?4:0)|(e.name==null?8:0),r=new i(e.name||"",t,e.id,n);if(e.props){for(let s of e.props)if(Array.isArray(s)||(s=s(r)),s){if(s[0].perNode)throw new RangeError("Can't store a per-node prop on a node type");t[s[0].id]=s[1]}}return r}prop(e){return this.props[e.id]}get isTop(){return(this.flags&1)>0}get isSkipped(){return(this.flags&2)>0}get isError(){return(this.flags&4)>0}get isAnonymous(){return(this.flags&8)>0}is(e){if(typeof e=="string"){if(this.name==e)return!0;let t=this.prop(U.group);return t?t.indexOf(e)>-1:!1}return this.id==e}static match(e){let t=Object.create(null);for(let n in e)for(let r of n.split(" "))t[r]=e[n];return n=>{for(let r=n.prop(U.group),s=-1;s<(r?r.length:0);s++){let o=t[s<0?n.name:r[s]];if(o)return o}}}};Oe.none=new Oe("",Object.create(null),0,8);var zi=class i{constructor(e){this.types=e;for(let t=0;t<e.length;t++)if(e[t].id!=t)throw new RangeError("Node type ids should correspond to array positions when creating a node set")}extend(...e){let t=[];for(let n of this.types){let r=null;for(let s of e){let o=s(n);if(o){r||(r=Object.assign({},n.props));let l=o[1],a=o[0];a.combine&&a.id in r&&(l=a.combine(r[a.id],l)),r[a.id]=l}}t.push(r?new Oe(n.name,r,n.id,n.flags):n)}return new i(t)}},sc=new WeakMap,ib=new WeakMap,F;(function(i){i[i.ExcludeBuffers=1]="ExcludeBuffers",i[i.IncludeAnonymous=2]="IncludeAnonymous",i[i.IgnoreMounts=4]="IgnoreMounts",i[i.IgnoreOverlays=8]="IgnoreOverlays",i[i.EnterBracketed=16]="EnterBracketed"})(F||(F={}));var J=class i{constructor(e,t,n,r,s){if(this.type=e,this.children=t,this.positions=n,this.length=r,this.props=null,s&&s.length){this.props=Object.create(null);for(let[o,l]of s)this.props[typeof o=="number"?o:o.id]=l}}toString(){let e=Yi.get(this);if(e&&!e.overlay)return e.tree.toString();let t="";for(let n of this.children){let r=n.toString();r&&(t&&(t+=","),t+=r)}return this.type.name?(/\W/.test(this.type.name)&&!this.type.isError?JSON.stringify(this.type.name):this.type.name)+(t.length?"("+t+")":""):t}cursor(e=0){return new ns(this.topNode,e)}cursorAt(e,t=0,n=0){let r=sc.get(this)||this.topNode,s=new ns(r);return s.moveTo(e,t),sc.set(this,s._tree),s}get topNode(){return new ft(this,0,0,null)}resolve(e,t=0){let n=Qo(sc.get(this)||this.topNode,e,t,!1);return sc.set(this,n),n}resolveInner(e,t=0){let n=Qo(ib.get(this)||this.topNode,e,t,!0);return ib.set(this,n),n}resolveStack(e,t=0){return YP(this,e,t)}iterate(e){let{enter:t,leave:n,from:r=0,to:s=this.length}=e,o=e.mode||0,l=(o&F.IncludeAnonymous)>0;for(let a=this.cursor(o|F.IncludeAnonymous);;){let c=!1;if(a.from<=s&&a.to>=r&&(!l&&a.type.isAnonymous||t(a)!==!1)){if(a.firstChild())continue;c=!0}for(;c&&n&&(l||!a.type.isAnonymous)&&n(a),!a.nextSibling();){if(!a.parent())return;c=!0}}}prop(e){return e.perNode?this.props?this.props[e.id]:void 0:this.type.prop(e)}get propValues(){let e=[];if(this.props)for(let t in this.props)e.push([+t,this.props[t]]);return e}balance(e={}){return this.children.length<=8?this:bd(Oe.none,this.children,this.positions,0,this.children.length,0,this.length,(t,n,r)=>new i(this.type,t,n,r,this.propValues),e.makeTree||((t,n,r)=>new i(Oe.none,t,n,r)))}static build(e){return zP(e)}};J.empty=new J(Oe.none,[],[],0);var ad=class i{constructor(e,t){this.buffer=e,this.index=t}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}get pos(){return this.index}next(){this.index-=4}fork(){return new i(this.buffer,this.index)}},Wi=class i{constructor(e,t,n){this.buffer=e,this.length=t,this.set=n}get type(){return Oe.none}toString(){let e=[];for(let t=0;t<this.buffer.length;)e.push(this.childString(t)),t=this.buffer[t+3];return e.join(",")}childString(e){let t=this.buffer[e],n=this.buffer[e+3],r=this.set.types[t],s=r.name;if(/\W/.test(s)&&!r.isError&&(s=JSON.stringify(s)),e+=4,n==e)return s;let o=[];for(;e<n;)o.push(this.childString(e)),e=this.buffer[e+3];return s+"("+o.join(",")+")"}findChild(e,t,n,r,s){let{buffer:o}=this,l=-1;for(let a=e;a!=t&&!(cb(s,r,o[a+1],o[a+2])&&(l=a,n>0));a=o[a+3]);return l}slice(e,t,n){let r=this.buffer,s=new Uint16Array(t-e),o=0;for(let l=e,a=0;l<t;){s[a++]=r[l++],s[a++]=r[l++]-n;let c=s[a++]=r[l++]-n;s[a++]=r[l++]-e,o=Math.max(o,c)}return new i(s,o,this.set)}};function cb(i,e,t,n){switch(i){case-2:return t<e;case-1:return n>=e&&t<e;case 0:return t<e&&n>e;case 1:return t<=e&&n>e;case 2:return n>e;case 4:return!0}}function Qo(i,e,t,n){for(var r;i.from==i.to||(t<1?i.from>=e:i.from>e)||(t>-1?i.to<=e:i.to<e);){let o=!n&&i instanceof ft&&i.index<0?null:i.parent;if(!o)return i;i=o}let s=n?0:F.IgnoreOverlays;if(n)for(let o=i,l=o.parent;l;o=l,l=o.parent)o instanceof ft&&o.index<0&&((r=l.enter(e,t,s))===null||r===void 0?void 0:r.from)!=o.from&&(i=l);for(;;){let o=i.enter(e,t,s);if(!o)return i;i=o}}var lc=class{cursor(e=0){return new ns(this,e)}getChild(e,t=null,n=null){let r=nb(this,e,t,n);return r.length?r[0]:null}getChildren(e,t=null,n=null){return nb(this,e,t,n)}resolve(e,t=0){return Qo(this,e,t,!1)}resolveInner(e,t=0){return Qo(this,e,t,!0)}matchContext(e){return cd(this.parent,e)}enterUnfinishedNodesBefore(e){let t=this.childBefore(e),n=this;for(;t;){let r=t.lastChild;if(!r||r.to!=t.to)break;r.type.isError&&r.from==r.to?(n=t,t=r.prevSibling):t=r}return n}get node(){return this}get next(){return this.parent}},ft=class i extends lc{constructor(e,t,n,r){super(),this._tree=e,this.from=t,this.index=n,this._parent=r}get type(){return this._tree.type}get name(){return this._tree.type.name}get to(){return this.from+this._tree.length}nextChild(e,t,n,r,s=0){for(let o=this;;){for(let{children:l,positions:a}=o._tree,c=t>0?l.length:-1;e!=c;e+=t){let h=l[e],f=a[e]+o.from,u;if(!(!(s&F.EnterBracketed&&h instanceof J&&(u=Yi.get(h))&&!u.overlay&&u.bracketed&&n>=f&&n<=f+h.length)&&!cb(r,n,f,f+h.length))){if(h instanceof Wi){if(s&F.ExcludeBuffers)continue;let d=h.findChild(0,h.buffer.length,t,n-f,r);if(d>-1)return new zn(new hd(o,h,e,f),null,d)}else if(s&F.IncludeAnonymous||!h.type.isAnonymous||md(h)){let d;if(!(s&F.IgnoreMounts)&&(d=Yi.get(h))&&!d.overlay)return new i(d.tree,f,e,o);let p=new i(h,f,e,o);return s&F.IncludeAnonymous||!p.type.isAnonymous?p:p.nextChild(t<0?h.children.length-1:0,t,n,r,s)}}}if(s&F.IncludeAnonymous||!o.type.isAnonymous||(o.index>=0?e=o.index+t:e=t<0?-1:o._parent._tree.children.length,o=o._parent,!o))return null}}get firstChild(){return this.nextChild(0,1,0,4)}get lastChild(){return this.nextChild(this._tree.children.length-1,-1,0,4)}childAfter(e){return this.nextChild(0,1,e,2)}childBefore(e){return this.nextChild(this._tree.children.length-1,-1,e,-2)}prop(e){return this._tree.prop(e)}enter(e,t,n=0){let r;if(!(n&F.IgnoreOverlays)&&(r=Yi.get(this._tree))&&r.overlay){let s=e-this.from,o=n&F.EnterBracketed&&r.bracketed;for(let{from:l,to:a}of r.overlay)if((t>0||o?l<=s:l<s)&&(t<0||o?a>=s:a>s))return new i(r.tree,r.overlay[0].from+this.from,-1,this)}return this.nextChild(0,1,e,t,n)}nextSignificantParent(){let e=this;for(;e.type.isAnonymous&&e._parent;)e=e._parent;return e}get parent(){return this._parent?this._parent.nextSignificantParent():null}get nextSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index+1,1,0,4):null}get prevSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index-1,-1,0,4):null}get tree(){return this._tree}toTree(){return this._tree}toString(){return this._tree.toString()}};function nb(i,e,t,n){let r=i.cursor(),s=[];if(!r.firstChild())return s;if(t!=null){for(let o=!1;!o;)if(o=r.type.is(t),!r.nextSibling())return s}for(;;){if(n!=null&&r.type.is(n))return s;if(r.type.is(e)&&s.push(r.node),!r.nextSibling())return n==null?s:[]}}function cd(i,e,t=e.length-1){for(let n=i;t>=0;n=n.parent){if(!n)return!1;if(!n.type.isAnonymous){if(e[t]&&e[t]!=n.name)return!1;t--}}return!0}var hd=class{constructor(e,t,n,r){this.parent=e,this.buffer=t,this.index=n,this.start=r}},zn=class i extends lc{get name(){return this.type.name}get from(){return this.context.start+this.context.buffer.buffer[this.index+1]}get to(){return this.context.start+this.context.buffer.buffer[this.index+2]}constructor(e,t,n){super(),this.context=e,this._parent=t,this.index=n,this.type=e.buffer.set.types[e.buffer.buffer[n]]}child(e,t,n){let{buffer:r}=this.context,s=r.findChild(this.index+4,r.buffer[this.index+3],e,t-this.context.start,n);return s<0?null:new i(this.context,this,s)}get firstChild(){return this.child(1,0,4)}get lastChild(){return this.child(-1,0,4)}childAfter(e){return this.child(1,e,2)}childBefore(e){return this.child(-1,e,-2)}prop(e){return this.type.prop(e)}enter(e,t,n=0){if(n&F.ExcludeBuffers)return null;let{buffer:r}=this.context,s=r.findChild(this.index+4,r.buffer[this.index+3],t>0?1:-1,e-this.context.start,t);return s<0?null:new i(this.context,this,s)}get parent(){return this._parent||this.context.parent.nextSignificantParent()}externalSibling(e){return this._parent?null:this.context.parent.nextChild(this.context.index+e,e,0,4)}get nextSibling(){let{buffer:e}=this.context,t=e.buffer[this.index+3];return t<(this._parent?e.buffer[this._parent.index+3]:e.buffer.length)?new i(this.context,this._parent,t):this.externalSibling(1)}get prevSibling(){let{buffer:e}=this.context,t=this._parent?this._parent.index+4:0;return this.index==t?this.externalSibling(-1):new i(this.context,this._parent,e.findChild(t,this.index,-1,0,4))}get tree(){return null}toTree(){let e=[],t=[],{buffer:n}=this.context,r=this.index+4,s=n.buffer[this.index+3];if(s>r){let o=n.buffer[this.index+1];e.push(n.slice(r,s,o)),t.push(0)}return new J(this.type,e,t,this.to-this.from)}toString(){return this.context.buffer.childString(this.index)}};function hb(i){if(!i.length)return null;let e=0,t=i[0];for(let s=1;s<i.length;s++){let o=i[s];(o.from>t.from||o.to<t.to)&&(t=o,e=s)}let n=t instanceof ft&&t.index<0?null:t.parent,r=i.slice();return n?r[e]=n:r.splice(e,1),new fd(r,t)}var fd=class{constructor(e,t){this.heads=e,this.node=t}get next(){return hb(this.heads)}};function YP(i,e,t){let n=i.resolveInner(e,t),r=null;for(let s=n instanceof ft?n:n.context.parent;s;s=s.parent)if(s.index<0){let o=s.parent;(r||(r=[n])).push(o.resolve(e,t)),s=o}else{let o=Yi.get(s.tree);if(o&&o.overlay&&o.overlay[0].from<=e&&o.overlay[o.overlay.length-1].to>=e){let l=new ft(o.tree,o.overlay[0].from+s.from,-1,s);(r||(r=[n])).push(Qo(l,e,t,!1))}}return r?hb(r):n}var ns=class{get name(){return this.type.name}constructor(e,t=0){if(this.buffer=null,this.stack=[],this.index=0,this.bufferNode=null,this.mode=t&~F.EnterBracketed,e instanceof ft)this.yieldNode(e);else{this._tree=e.context.parent,this.buffer=e.context;for(let n=e._parent;n;n=n._parent)this.stack.unshift(n.index);this.bufferNode=e,this.yieldBuf(e.index)}}yieldNode(e){return e?(this._tree=e,this.type=e.type,this.from=e.from,this.to=e.to,!0):!1}yieldBuf(e,t){this.index=e;let{start:n,buffer:r}=this.buffer;return this.type=t||r.set.types[r.buffer[e]],this.from=n+r.buffer[e+1],this.to=n+r.buffer[e+2],!0}yield(e){return e?e instanceof ft?(this.buffer=null,this.yieldNode(e)):(this.buffer=e.context,this.yieldBuf(e.index,e.type)):!1}toString(){return this.buffer?this.buffer.buffer.childString(this.index):this._tree.toString()}enterChild(e,t,n){if(!this.buffer)return this.yield(this._tree.nextChild(e<0?this._tree._tree.children.length-1:0,e,t,n,this.mode));let{buffer:r}=this.buffer,s=r.findChild(this.index+4,r.buffer[this.index+3],e,t-this.buffer.start,n);return s<0?!1:(this.stack.push(this.index),this.yieldBuf(s))}firstChild(){return this.enterChild(1,0,4)}lastChild(){return this.enterChild(-1,0,4)}childAfter(e){return this.enterChild(1,e,2)}childBefore(e){return this.enterChild(-1,e,-2)}enter(e,t,n=this.mode){return this.buffer?n&F.ExcludeBuffers?!1:this.enterChild(1,e,t):this.yield(this._tree.enter(e,t,n))}parent(){if(!this.buffer)return this.yieldNode(this.mode&F.IncludeAnonymous?this._tree._parent:this._tree.parent);if(this.stack.length)return this.yieldBuf(this.stack.pop());let e=this.mode&F.IncludeAnonymous?this.buffer.parent:this.buffer.parent.nextSignificantParent();return this.buffer=null,this.yieldNode(e)}sibling(e){if(!this.buffer)return this._tree._parent?this.yield(this._tree.index<0?null:this._tree._parent.nextChild(this._tree.index+e,e,0,4,this.mode)):!1;let{buffer:t}=this.buffer,n=this.stack.length-1;if(e<0){let r=n<0?0:this.stack[n]+4;if(this.index!=r)return this.yieldBuf(t.findChild(r,this.index,-1,0,4))}else{let r=t.buffer[this.index+3];if(r<(n<0?t.buffer.length:t.buffer[this.stack[n]+3]))return this.yieldBuf(r)}return n<0?this.yield(this.buffer.parent.nextChild(this.buffer.index+e,e,0,4,this.mode)):!1}nextSibling(){return this.sibling(1)}prevSibling(){return this.sibling(-1)}atLastNode(e){let t,n,{buffer:r}=this;if(r){if(e>0){if(this.index<r.buffer.buffer.length)return!1}else for(let s=0;s<this.index;s++)if(r.buffer.buffer[s+3]<this.index)return!1;({index:t,parent:n}=r)}else({index:t,_parent:n}=this._tree);for(;n;{index:t,_parent:n}=n)if(t>-1)for(let s=t+e,o=e<0?-1:n._tree.children.length;s!=o;s+=e){let l=n._tree.children[s];if(this.mode&F.IncludeAnonymous||l instanceof Wi||!l.type.isAnonymous||md(l))return!1}return!0}move(e,t){if(t&&this.enterChild(e,0,4))return!0;for(;;){if(this.sibling(e))return!0;if(this.atLastNode(e)||!this.parent())return!1}}next(e=!0){return this.move(1,e)}prev(e=!0){return this.move(-1,e)}moveTo(e,t=0){for(;(this.from==this.to||(t<1?this.from>=e:this.from>e)||(t>-1?this.to<=e:this.to<e))&&this.parent(););for(;this.enterChild(1,e,t););return this}get node(){if(!this.buffer)return this._tree;let e=this.bufferNode,t=null,n=0;if(e&&e.context==this.buffer)e:for(let r=this.index,s=this.stack.length;s>=0;){for(let o=e;o;o=o._parent)if(o.index==r){if(r==this.index)return o;t=o,n=s+1;break e}r=this.stack[--s]}for(let r=n;r<this.stack.length;r++)t=new zn(this.buffer,t,this.stack[r]);return this.bufferNode=new zn(this.buffer,t,this.index)}get tree(){return this.buffer?null:this._tree._tree}iterate(e,t){for(let n=0;;){let r=!1;if(this.type.isAnonymous||e(this)!==!1){if(this.firstChild()){n++;continue}this.type.isAnonymous||(r=!0)}for(;;){if(r&&t&&t(this),r=this.type.isAnonymous,!n)return;if(this.nextSibling())break;this.parent(),n--,r=!0}}}matchContext(e){if(!this.buffer)return cd(this.node.parent,e);let{buffer:t}=this.buffer,{types:n}=t.set;for(let r=e.length-1,s=this.stack.length-1;r>=0;s--){if(s<0)return cd(this._tree,e,r);let o=n[t.buffer[this.stack[s]]];if(!o.isAnonymous){if(e[r]&&e[r]!=o.name)return!1;r--}}return!0}};function md(i){return i.children.some(e=>e instanceof Wi||!e.type.isAnonymous||md(e))}function zP(i){var e;let{buffer:t,nodeSet:n,maxBufferLength:r=1024,reused:s=[],minRepeatType:o=n.types.length}=i,l=Array.isArray(t)?new ad(t,t.length):t,a=n.types,c=0,h=0;function f(C,T,y,Q,S,$){let{id:k,start:P,end:E,size:D}=l,V=h,N=c;if(D<0)if(l.next(),D==-1){let Gt=s[k];y.push(Gt),Q.push(P-C);return}else if(D==-3){c=k;return}else if(D==-4){h=k;return}else throw new RangeError(`Unrecognized record size: ${D}`);let j=a[k],H,ie,je=P-C;if(E-P<=r&&(ie=g(l.pos-T,S))){let Gt=new Uint16Array(ie.size-ie.skip),Ne=l.pos-ie.size,ye=Gt.length;for(;l.pos>Ne;)ye=b(ie.start,Gt,ye);H=new Wi(Gt,E-ie.start,n),je=ie.start-C}else{let Gt=l.pos-D;l.next();let Ne=[],ye=[],Ft=k>=o?k:-1,rr=0,sl=E;for(;l.pos>Gt;)Ft>=0&&l.id==Ft&&l.size>=0?(l.end<=sl-r&&(p(Ne,ye,P,rr,l.end,sl,Ft,V,N),rr=Ne.length,sl=l.end),l.next()):$>2500?u(P,Gt,Ne,ye):f(P,Gt,Ne,ye,Ft,$+1);if(Ft>=0&&rr>0&&rr<Ne.length&&p(Ne,ye,P,rr,P,sl,Ft,V,N),Ne.reverse(),ye.reverse(),Ft>-1&&rr>0){let SO=d(j,N);H=bd(j,Ne,ye,0,Ne.length,0,E-P,SO,SO)}else H=O(j,Ne,ye,E-P,V-E,N)}y.push(H),Q.push(je)}function u(C,T,y,Q){let S=[],$=0,k=-1;for(;l.pos>T;){let{id:P,start:E,end:D,size:V}=l;if(V>4)l.next();else{if(k>-1&&E<k)break;k<0&&(k=D-r),S.push(P,E,D),$++,l.next()}}if($){let P=new Uint16Array($*4),E=S[S.length-2];for(let D=S.length-3,V=0;D>=0;D-=3)P[V++]=S[D],P[V++]=S[D+1]-E,P[V++]=S[D+2]-E,P[V++]=V;y.push(new Wi(P,S[2]-E,n)),Q.push(E-C)}}function d(C,T){return(y,Q,S)=>{let $=0,k=y.length-1,P,E;if(k>=0&&(P=y[k])instanceof J){if(!k&&P.type==C&&P.length==S)return P;(E=P.prop(U.lookAhead))&&($=Q[k]+P.length+E)}return O(C,y,Q,S,$,T)}}function p(C,T,y,Q,S,$,k,P,E){let D=[],V=[];for(;C.length>Q;)D.push(C.pop()),V.push(T.pop()+y-S);C.push(O(n.types[k],D,V,$-S,P-$,E)),T.push(S-y)}function O(C,T,y,Q,S,$,k){if($){let P=[U.contextHash,$];k=k?[P].concat(k):[P]}if(S>25){let P=[U.lookAhead,S];k=k?[P].concat(k):[P]}return new J(C,T,y,Q,k)}function g(C,T){let y=l.fork(),Q=0,S=0,$=0,k=y.end-r,P={size:0,start:0,skip:0};e:for(let E=y.pos-C;y.pos>E;){let D=y.size;if(y.id==T&&D>=0){P.size=Q,P.start=S,P.skip=$,$+=4,Q+=4,y.next();continue}let V=y.pos-D;if(D<0||V<E||y.start<k)break;let N=y.id>=o?4:0,j=y.start;for(y.next();y.pos>V;){if(y.size<0)if(y.size==-3||y.size==-4)N+=4;else break e;else y.id>=o&&(N+=4);y.next()}S=j,Q+=D,$+=N}return(T<0||Q==C)&&(P.size=Q,P.start=S,P.skip=$),P.size>4?P:void 0}function b(C,T,y){let{id:Q,start:S,end:$,size:k}=l;if(l.next(),k>=0&&Q<o){let P=y;if(k>4){let E=l.pos-(k-4);for(;l.pos>E;)y=b(C,T,y)}T[--y]=P,T[--y]=$-C,T[--y]=S-C,T[--y]=Q}else k==-3?c=Q:k==-4&&(h=Q);return y}let w=[],x=[];for(;l.pos>0;)f(i.start||0,i.bufferStart||0,w,x,-1,0);let X=(e=i.length)!==null&&e!==void 0?e:w.length?x[0]+w[0].length:0;return new J(a[i.topID],w.reverse(),x.reverse(),X)}var rb=new WeakMap;function oc(i,e){if(!i.isAnonymous||e instanceof Wi||e.type!=i)return 1;let t=rb.get(e);if(t==null){t=1;for(let n of e.children){if(n.type!=i||!(n instanceof J)){t=1;break}t+=oc(i,n)}rb.set(e,t)}return t}function bd(i,e,t,n,r,s,o,l,a){let c=0;for(let p=n;p<r;p++)c+=oc(i,e[p]);let h=Math.ceil(c*1.5/8),f=[],u=[];function d(p,O,g,b,w){for(let x=g;x<b;){let X=x,C=O[x],T=oc(i,p[x]);for(x++;x<b;x++){let y=oc(i,p[x]);if(T+y>=h)break;T+=y}if(x==X+1){if(T>h){let y=p[X];d(y.children,y.positions,0,y.children.length,O[X]+w);continue}f.push(p[X])}else{let y=O[x-1]+p[x-1].length-C;f.push(bd(i,p,O,X,x,C,y,null,a))}u.push(C+w-s)}}return d(e,t,n,r,0),(l||a)(f,u,o)}var rs=class{constructor(){this.map=new WeakMap}setBuffer(e,t,n){let r=this.map.get(e);r||this.map.set(e,r=new Map),r.set(t,n)}getBuffer(e,t){let n=this.map.get(e);return n&&n.get(t)}set(e,t){e instanceof zn?this.setBuffer(e.context.buffer,e.index,t):e instanceof ft&&this.map.set(e.tree,t)}get(e){return e instanceof zn?this.getBuffer(e.context.buffer,e.index):e instanceof ft?this.map.get(e.tree):void 0}cursorSet(e,t){e.buffer?this.setBuffer(e.buffer.buffer,e.index,t):this.map.set(e.tree,t)}cursorGet(e){return e.buffer?this.getBuffer(e.buffer.buffer,e.index):this.map.get(e.tree)}},vi=class i{constructor(e,t,n,r,s=!1,o=!1){this.from=e,this.to=t,this.tree=n,this.offset=r,this.open=(s?1:0)|(o?2:0)}get openStart(){return(this.open&1)>0}get openEnd(){return(this.open&2)>0}static addTree(e,t=[],n=!1){let r=[new i(0,e.length,e,0,!1,n)];for(let s of t)s.to>e.length&&r.push(s);return r}static applyChanges(e,t,n=128){if(!t.length)return e;let r=[],s=1,o=e.length?e[0]:null;for(let l=0,a=0,c=0;;l++){let h=l<t.length?t[l]:null,f=h?h.fromA:1e9;if(f-a>=n)for(;o&&o.from<f;){let u=o;if(a>=u.from||f<=u.to||c){let d=Math.max(u.from,a)-c,p=Math.min(u.to,f)-c;u=d>=p?null:new i(d,p,u.tree,u.offset+c,l>0,!!h)}if(u&&r.push(u),o.to>f)break;o=s<e.length?e[s++]:null}if(!h)break;a=h.toA,c=h.toA-h.toB}return r}},Ni=class{startParse(e,t,n){return typeof e=="string"&&(e=new ud(e)),n=n?n.length?n.map(r=>new ht(r.from,r.to)):[new ht(0,0)]:[new ht(0,e.length)],this.createParse(e,t||[],n)}parse(e,t,n){let r=this.startParse(e,t,n);for(;;){let s=r.advance();if(s)return s}}},ud=class{constructor(e){this.string=e}get length(){return this.string.length}chunk(e){return this.string.slice(e)}get lineChunks(){return!1}read(e,t){return this.string.slice(e,t)}};function hc(i){return(e,t,n,r)=>new Od(e,i,t,n,r)}var ac=class{constructor(e,t,n,r,s,o){this.parser=e,this.parse=t,this.overlay=n,this.bracketed=r,this.target=s,this.from=o}};function sb(i){if(!i.length||i.some(e=>e.from>=e.to))throw new RangeError("Invalid inner parse ranges given: "+JSON.stringify(i))}var dd=class{constructor(e,t,n,r,s,o,l,a){this.parser=e,this.predicate=t,this.mounts=n,this.index=r,this.start=s,this.bracketed=o,this.target=l,this.prev=a,this.depth=0,this.ranges=[]}},pd=new U({perNode:!0}),Od=class{constructor(e,t,n,r,s){this.nest=t,this.input=n,this.fragments=r,this.ranges=s,this.inner=[],this.innerDone=0,this.baseTree=null,this.stoppedAt=null,this.baseParse=e}advance(){if(this.baseParse){let n=this.baseParse.advance();if(!n)return null;if(this.baseParse=null,this.baseTree=n,this.startInner(),this.stoppedAt!=null)for(let r of this.inner)r.parse.stopAt(this.stoppedAt)}if(this.innerDone==this.inner.length){let n=this.baseTree;return this.stoppedAt!=null&&(n=new J(n.type,n.children,n.positions,n.length,n.propValues.concat([[pd,this.stoppedAt]]))),n}let e=this.inner[this.innerDone],t=e.parse.advance();if(t){this.innerDone++;let n=Object.assign(Object.create(null),e.target.props);n[U.mounted.id]=new Yi(t,e.overlay,e.parser,e.bracketed),e.target.props=n}return null}get parsedPos(){if(this.baseParse)return 0;let e=this.input.length;for(let t=this.innerDone;t<this.inner.length;t++)this.inner[t].from<e&&(e=Math.min(e,this.inner[t].parse.parsedPos));return e}stopAt(e){if(this.stoppedAt=e,this.baseParse)this.baseParse.stopAt(e);else for(let t=this.innerDone;t<this.inner.length;t++)this.inner[t].parse.stopAt(e)}startInner(){let e=new gd(this.fragments),t=null,n=null,r=new ns(new ft(this.baseTree,this.ranges[0].from,0,null),F.IncludeAnonymous|F.IgnoreMounts);e:for(let s,o;;){let l=!0,a;if(this.stoppedAt!=null&&r.from>=this.stoppedAt)l=!1;else if(e.hasNode(r)){if(t){let c=t.mounts.find(h=>h.frag.from<=r.from&&h.frag.to>=r.to&&h.mount.overlay);if(c)for(let h of c.mount.overlay){let f=h.from+c.pos,u=h.to+c.pos;f>=r.from&&u<=r.to&&!t.ranges.some(d=>d.from<u&&d.to>f)&&t.ranges.push({from:f,to:u})}}l=!1}else if(n&&(o=WP(n.ranges,r.from,r.to)))l=o!=2;else if(!r.type.isAnonymous&&(s=this.nest(r,this.input))&&(r.from<r.to||!s.overlay)){r.tree||(NP(r),t&&t.depth++,n&&n.depth++);let c=e.findMounts(r.from,s.parser);if(typeof s.overlay=="function")t=new dd(s.parser,s.overlay,c,this.inner.length,r.from,!!s.bracketed,r.tree,t);else{let h=lb(this.ranges,s.overlay||(r.from<r.to?[new ht(r.from,r.to)]:[]));h.length&&sb(h),(h.length||!s.overlay)&&this.inner.push(new ac(s.parser,h.length?s.parser.startParse(this.input,ab(c,h),h):s.parser.startParse(""),s.overlay?s.overlay.map(f=>new ht(f.from-r.from,f.to-r.from)):null,!!s.bracketed,r.tree,h.length?h[0].from:r.from)),s.overlay?h.length&&(n={ranges:h,depth:0,prev:n}):l=!1}}else if(t&&(a=t.predicate(r))&&(a===!0&&(a=new ht(r.from,r.to)),a.from<a.to)){let c=t.ranges.length-1;c>=0&&t.ranges[c].to==a.from?t.ranges[c]={from:t.ranges[c].from,to:a.to}:t.ranges.push(a)}if(l&&r.firstChild())t&&t.depth++,n&&n.depth++;else for(;!r.nextSibling();){if(!r.parent())break e;if(t&&!--t.depth){let c=lb(this.ranges,t.ranges);c.length&&(sb(c),this.inner.splice(t.index,0,new ac(t.parser,t.parser.startParse(this.input,ab(t.mounts,c),c),t.ranges.map(h=>new ht(h.from-t.start,h.to-t.start)),t.bracketed,t.target,c[0].from))),t=t.prev}n&&!--n.depth&&(n=n.prev)}}}};function WP(i,e,t){for(let n of i){if(n.from>=t)break;if(n.to>e)return n.from<=e&&n.to>=t?2:1}return 0}function ob(i,e,t,n,r,s){if(e<t){let o=i.buffer[e+1];n.push(i.slice(e,t,o)),r.push(o-s)}}function NP(i){let{node:e}=i,t=[],n=e.context.buffer;do t.push(i.index),i.parent();while(!i.tree);let r=i.tree,s=r.children.indexOf(n),o=r.children[s],l=o.buffer,a=[s];function c(h,f,u,d,p,O){let g=t[O],b=[],w=[];ob(o,h,g,b,w,d);let x=l[g+1],X=l[g+2];a.push(b.length);let C=O?c(g+4,l[g+3],o.set.types[l[g]],x,X-x,O-1):e.toTree();return b.push(C),w.push(x-d),ob(o,l[g+3],f,b,w,d),new J(u,b,w,p)}r.children[s]=c(0,l.length,Oe.none,0,o.length,t.length-1);for(let h of a){let f=i.tree.children[h],u=i.tree.positions[h];i.yield(new ft(f,u+i.from,h,i._tree))}}var cc=class{constructor(e,t){this.offset=t,this.done=!1,this.cursor=e.cursor(F.IncludeAnonymous|F.IgnoreMounts|F.ExcludeBuffers)}moveTo(e){let{cursor:t}=this,n=e-this.offset;for(;!this.done&&t.from<n;)if(!(t.to>=n&&t.enter(n,1,F.IncludeAnonymous|F.IgnoreOverlays|F.ExcludeBuffers)))if(t.to<=n)t.next(!1)||(this.done=!0);else break}hasNode(e){if(this.moveTo(e.from),!this.done&&this.cursor.from+this.offset==e.from&&this.cursor.tree)for(let t=this.cursor.tree;;){if(t==e.tree)return!0;if(t.children.length&&t.positions[0]==0&&t.children[0]instanceof J)t=t.children[0];else break}return!1}},gd=class{constructor(e){var t;if(this.fragments=e,this.curTo=0,this.fragI=0,e.length){let n=this.curFrag=e[0];this.curTo=(t=n.tree.prop(pd))!==null&&t!==void 0?t:n.to,this.inner=new cc(n.tree,-n.offset)}else this.curFrag=this.inner=null}hasNode(e){for(;this.curFrag&&e.from>=this.curTo;)this.nextFrag();return this.curFrag&&this.curFrag.from<=e.from&&this.curTo>=e.to&&this.inner.hasNode(e)}nextFrag(){var e;if(this.fragI++,this.fragI==this.fragments.length)this.curFrag=this.inner=null;else{let t=this.curFrag=this.fragments[this.fragI];this.curTo=(e=t.tree.prop(pd))!==null&&e!==void 0?e:t.to,this.inner=new cc(t.tree,-t.offset)}}findMounts(e,t){var n;let r=[];if(this.inner){this.inner.cursor.moveTo(e,1);for(let s=this.inner.cursor.node;s;s=s.parent){let o=(n=s.tree)===null||n===void 0?void 0:n.prop(U.mounted);if(o&&o.parser==t)for(let l=this.fragI;l<this.fragments.length;l++){let a=this.fragments[l];if(a.from>=s.to)break;a.tree==this.curFrag.tree&&r.push({frag:a,pos:s.from-a.offset,mount:o})}}}return r}};function lb(i,e){let t=null,n=e;for(let r=1,s=0;r<i.length;r++){let o=i[r-1].to,l=i[r].from;for(;s<n.length;s++){let a=n[s];if(a.from>=l)break;a.to<=o||(t||(n=t=e.slice()),a.from<o?(t[s]=new ht(a.from,o),a.to>l&&t.splice(s+1,0,new ht(l,a.to))):a.to>l?t[s--]=new ht(l,a.to):t.splice(s--,1))}}return n}function qP(i,e,t,n){let r=0,s=0,o=!1,l=!1,a=-1e9,c=[];for(;;){let h=r==i.length?1e9:o?i[r].to:i[r].from,f=s==e.length?1e9:l?e[s].to:e[s].from;if(o!=l){let u=Math.max(a,t),d=Math.min(h,f,n);u<d&&c.push(new ht(u,d))}if(a=Math.min(h,f),a==1e9)break;h==a&&(o?(o=!1,r++):o=!0),f==a&&(l?(l=!1,s++):l=!0)}return c}function ab(i,e){let t=[];for(let{pos:n,mount:r,frag:s}of i){let o=n+(r.overlay?r.overlay[0].from:0),l=o+r.tree.length,a=Math.max(s.from,o),c=Math.min(s.to,l);if(r.overlay){let h=r.overlay.map(u=>new ht(u.from+n,u.to+n)),f=qP(e,h,a,c);for(let u=0,d=a;;u++){let p=u==f.length,O=p?c:f[u].from;if(O>d&&t.push(new vi(d,O,r.tree,-o,s.from>=d||s.openStart,s.to<=O||s.openEnd)),p)break;d=f[u].to}}else t.push(new vi(a,c,r.tree,-o,s.from>=o||s.openStart,s.to<=l||s.openEnd))}return t}var GP=0,St=class i{constructor(e,t,n,r){this.name=e,this.set=t,this.base=n,this.modified=r,this.id=GP++}toString(){let{name:e}=this;for(let t of this.modified)t.name&&(e=`${t.name}(${e})`);return e}static define(e,t){let n=typeof e=="string"?e:"?";if(e instanceof i&&(t=e),t?.base)throw new Error("Can not derive from a modified tag");let r=new i(n,[],null,[]);if(r.set.push(r),t)for(let s of t.set)r.set.push(s);return r}static defineModifier(e){let t=new pc(e);return n=>n.modified.indexOf(t)>-1?n:pc.get(n.base||n,n.modified.concat(t).sort((r,s)=>r.id-s.id))}},FP=0,pc=class i{constructor(e){this.name=e,this.instances=[],this.id=FP++}static get(e,t){if(!t.length)return e;let n=t[0].instances.find(l=>l.base==e&&HP(t,l.modified));if(n)return n;let r=[],s=new St(e.name,r,e,t);for(let l of t)l.instances.push(s);let o=KP(t);for(let l of e.set)if(!l.modified.length)for(let a of o)r.push(i.get(l,a));return s}};function HP(i,e){return i.length==e.length&&i.every((t,n)=>t==e[n])}function KP(i){let e=[[]];for(let t=0;t<i.length;t++)for(let n=0,r=e.length;n<r;n++)e.push(e[n].concat(i[t]));return e.sort((t,n)=>n.length-t.length)}function Bt(i){let e=Object.create(null);for(let t in i){let n=i[t];Array.isArray(n)||(n=[n]);for(let r of t.split(" "))if(r){let s=[],o=2,l=r;for(let f=0;;){if(l=="..."&&f>0&&f+3==r.length){o=1;break}let u=/^"(?:[^"\\]|\\.)*?"|[^\/!]+/.exec(l);if(!u)throw new RangeError("Invalid path: "+r);if(s.push(u[0]=="*"?"":u[0][0]=='"'?JSON.parse(u[0]):u[0]),f+=u[0].length,f==r.length)break;let d=r[f++];if(f==r.length&&d=="!"){o=0;break}if(d!="/")throw new RangeError("Invalid path: "+r);l=r.slice(f)}let a=s.length-1,c=s[a];if(!c)throw new RangeError("Invalid path: "+r);let h=new Nn(n,o,a>0?s.slice(0,a):null);e[c]=h.sort(e[c])}}return db.add(e)}var db=new U({combine(i,e){let t,n,r;for(;i||e;){if(!i||e&&i.depth<=e.depth?(r=e,e=e.next):(r=i,i=i.next),t&&t.mode==r.mode&&!r.context&&!t.context)continue;let s=new Nn(r.tags,r.mode,r.context);t?t.next=s:n=s,t=s}return n}}),Nn=class{constructor(e,t,n,r){this.tags=e,this.mode=t,this.context=n,this.next=r}get opaque(){return this.mode==0}get inherit(){return this.mode==1}sort(e){return!e||e.depth<this.depth?(this.next=e,this):(e.next=this.sort(e.next),e)}get depth(){return this.context?this.context.length:0}};Nn.empty=new Nn([],2,null);function wd(i,e){let t=Object.create(null);for(let s of i)if(!Array.isArray(s.tag))t[s.tag.id]=s.class;else for(let o of s.tag)t[o.id]=s.class;let{scope:n,all:r=null}=e||{};return{style:s=>{let o=r;for(let l of s)for(let a of l.set){let c=t[a.id];if(c){o=o?o+" "+c:c;break}}return o},scope:n}}function JP(i,e){let t=null;for(let n of i){let r=n.style(e);r&&(t=t?t+" "+r:r)}return t}function pb(i,e,t,n=0,r=i.length){let s=new Sd(n,Array.isArray(e)?e:[e],t);s.highlightRange(i.cursor(),n,r,"",s.highlighters),s.flush(r)}var Sd=class{constructor(e,t,n){this.at=e,this.highlighters=t,this.span=n,this.class=""}startSpan(e,t){t!=this.class&&(this.flush(e),e>this.at&&(this.at=e),this.class=t)}flush(e){e>this.at&&this.class&&this.span(this.at,e,this.class)}highlightRange(e,t,n,r,s){let{type:o,from:l,to:a}=e;if(l>=n||a<=t)return;o.isTop&&(s=this.highlighters.filter(d=>!d.scope||d.scope(o)));let c=r,h=eC(e)||Nn.empty,f=JP(s,h.tags);if(f&&(c&&(c+=" "),c+=f,h.mode==1&&(r+=(r?" ":"")+f)),this.startSpan(Math.max(t,l),c),h.opaque)return;let u=e.tree&&e.tree.prop(U.mounted);if(u&&u.overlay){let d=e.node.enter(u.overlay[0].from+l,1),p=this.highlighters.filter(g=>!g.scope||g.scope(u.tree.type)),O=e.firstChild();for(let g=0,b=l;;g++){let w=g<u.overlay.length?u.overlay[g]:null,x=w?w.from+l:a,X=Math.max(t,b),C=Math.min(n,x);if(X<C&&O)for(;e.from<C&&(this.highlightRange(e,X,C,r,s),this.startSpan(Math.min(C,e.to),c),!(e.to>=x||!e.nextSibling())););if(!w||x>n)break;b=w.to+l,b>t&&(this.highlightRange(d.cursor(),Math.max(t,w.from+l),Math.min(n,b),"",p),this.startSpan(Math.min(n,b),c))}O&&e.parent()}else if(e.firstChild()){u&&(r="");do if(!(e.to<=t)){if(e.from>=n)break;this.highlightRange(e,t,n,r,s),this.startSpan(Math.min(n,e.to),c)}while(e.nextSibling());e.parent()}}};function eC(i){let e=i.type.prop(db);for(;e&&e.context&&!i.matchContext(e.context);)e=e.next;return e||null}var Z=St.define,fc=Z(),qi=Z(),fb=Z(qi),ub=Z(qi),Gi=Z(),uc=Z(Gi),yd=Z(Gi),hi=Z(),Wn=Z(hi),ai=Z(),ci=Z(),xd=Z(),vo=Z(xd),dc=Z(),m={comment:fc,lineComment:Z(fc),blockComment:Z(fc),docComment:Z(fc),name:qi,variableName:Z(qi),typeName:fb,tagName:Z(fb),propertyName:ub,attributeName:Z(ub),className:Z(qi),labelName:Z(qi),namespace:Z(qi),macroName:Z(qi),literal:Gi,string:uc,docString:Z(uc),character:Z(uc),attributeValue:Z(uc),number:yd,integer:Z(yd),float:Z(yd),bool:Z(Gi),regexp:Z(Gi),escape:Z(Gi),color:Z(Gi),url:Z(Gi),keyword:ai,self:Z(ai),null:Z(ai),atom:Z(ai),unit:Z(ai),modifier:Z(ai),operatorKeyword:Z(ai),controlKeyword:Z(ai),definitionKeyword:Z(ai),moduleKeyword:Z(ai),operator:ci,derefOperator:Z(ci),arithmeticOperator:Z(ci),logicOperator:Z(ci),bitwiseOperator:Z(ci),compareOperator:Z(ci),updateOperator:Z(ci),definitionOperator:Z(ci),typeOperator:Z(ci),controlOperator:Z(ci),punctuation:xd,separator:Z(xd),bracket:vo,angleBracket:Z(vo),squareBracket:Z(vo),paren:Z(vo),brace:Z(vo),content:hi,heading:Wn,heading1:Z(Wn),heading2:Z(Wn),heading3:Z(Wn),heading4:Z(Wn),heading5:Z(Wn),heading6:Z(Wn),contentSeparator:Z(hi),list:Z(hi),quote:Z(hi),emphasis:Z(hi),strong:Z(hi),link:Z(hi),monospace:Z(hi),strikethrough:Z(hi),inserted:Z(),deleted:Z(),changed:Z(),invalid:Z(),meta:dc,documentMeta:Z(dc),annotation:Z(dc),processingInstruction:Z(dc),definition:St.defineModifier("definition"),constant:St.defineModifier("constant"),function:St.defineModifier("function"),standard:St.defineModifier("standard"),local:St.defineModifier("local"),special:St.defineModifier("special")};for(let i in m){let e=m[i];e instanceof St&&(e.name=i)}var _X=wd([{tag:m.link,class:"tok-link"},{tag:m.heading,class:"tok-heading"},{tag:m.emphasis,class:"tok-emphasis"},{tag:m.strong,class:"tok-strong"},{tag:m.keyword,class:"tok-keyword"},{tag:m.atom,class:"tok-atom"},{tag:m.bool,class:"tok-bool"},{tag:m.url,class:"tok-url"},{tag:m.labelName,class:"tok-labelName"},{tag:m.inserted,class:"tok-inserted"},{tag:m.deleted,class:"tok-deleted"},{tag:m.literal,class:"tok-literal"},{tag:m.string,class:"tok-string"},{tag:m.number,class:"tok-number"},{tag:[m.regexp,m.escape,m.special(m.string)],class:"tok-string2"},{tag:m.variableName,class:"tok-variableName"},{tag:m.local(m.variableName),class:"tok-variableName tok-local"},{tag:m.definition(m.variableName),class:"tok-variableName tok-definition"},{tag:m.special(m.variableName),class:"tok-variableName2"},{tag:m.definition(m.propertyName),class:"tok-propertyName tok-definition"},{tag:m.typeName,class:"tok-typeName"},{tag:m.namespace,class:"tok-namespace"},{tag:m.className,class:"tok-className"},{tag:m.macroName,class:"tok-macroName"},{tag:m.propertyName,class:"tok-propertyName"},{tag:m.operator,class:"tok-operator"},{tag:m.comment,class:"tok-comment"},{tag:m.meta,class:"tok-meta"},{tag:m.invalid,class:"tok-invalid"},{tag:m.punctuation,class:"tok-punctuation"}]);var kd,Fi=new U;function Mo(i){return R.define({combine:i?e=>e.concat(i):void 0})}var bc=new U,tt=class{constructor(e,t,n=[],r=""){this.data=e,this.name=r,ne.prototype.hasOwnProperty("tree")||Object.defineProperty(ne.prototype,"tree",{get(){return ee(this)}}),this.parser=t,this.extension=[Ki.of(this),ne.languageData.of((s,o,l)=>{let a=Ob(s,o,l),c=a.type.prop(Fi);if(!c)return[];let h=s.facet(c),f=a.type.prop(bc);if(f){let u=a.resolve(o-a.from,l);for(let d of f)if(d.test(u,s)){let p=s.facet(d.facet);return d.type=="replace"?p:p.concat(h)}}return h})].concat(n)}isActiveAt(e,t,n=-1){return Ob(e,t,n).type.prop(Fi)==this.data}findRegions(e){let t=e.facet(Ki);if(t?.data==this.data)return[{from:0,to:e.doc.length}];if(!t||!t.allowsNesting)return[];let n=[],r=(s,o)=>{if(s.prop(Fi)==this.data){n.push({from:o,to:o+s.length});return}let l=s.prop(U.mounted);if(l){if(l.tree.prop(Fi)==this.data){if(l.overlay)for(let a of l.overlay)n.push({from:a.from+o,to:a.to+o});else n.push({from:o,to:o+s.length});return}else if(l.overlay){let a=n.length;if(r(l.tree,l.overlay[0].from+o),n.length>a)return}}for(let a=0;a<s.children.length;a++){let c=s.children[a];c instanceof J&&r(c,s.positions[a]+o)}};return r(ee(e),0),n}get allowsNesting(){return!0}};tt.setState=Y.define();function Ob(i,e,t){let n=i.facet(Ki),r=ee(i).topNode;if(!n||n.allowsNesting)for(let s=r;s;s=s.enter(e,t,F.ExcludeBuffers|F.EnterBracketed))s.type.isTop&&(r=s);return r}var Hi=class i extends tt{constructor(e,t,n){super(e,t,[],n),this.parser=t}static define(e){let t=Mo(e.languageData);return new i(t,e.parser.configure({props:[Fi.add(n=>n.isTop?t:void 0)]}),e.name)}configure(e,t){return new i(this.data,this.parser.configure(e),t||this.name)}get allowsNesting(){return this.parser.hasWrappers()}};function ee(i){let e=i.field(tt.state,!1);return e?e.tree:J.empty}var Pd=class{constructor(e){this.doc=e,this.cursorPos=0,this.string="",this.cursor=e.iter()}get length(){return this.doc.length}syncTo(e){return this.string=this.cursor.next(e-this.cursorPos).value,this.cursorPos=e+this.string.length,this.cursorPos-this.string.length}chunk(e){return this.syncTo(e),this.string}get lineChunks(){return!0}read(e,t){let n=this.cursorPos-this.string.length;return e<n||t>=this.cursorPos?this.doc.sliceString(e,t):this.string.slice(e-n,t-n)}},$o=null,Co=class i{constructor(e,t,n=[],r,s,o,l,a){this.parser=e,this.state=t,this.fragments=n,this.tree=r,this.treeLen=s,this.viewport=o,this.skipped=l,this.scheduleOn=a,this.parse=null,this.tempSkipped=[]}static create(e,t,n){return new i(e,t,[],J.empty,0,n,[],null)}startParse(){return this.parser.startParse(new Pd(this.state.doc),this.fragments)}work(e,t){return t!=null&&t>=this.state.doc.length&&(t=void 0),this.tree!=J.empty&&this.isDone(t??this.state.doc.length)?(this.takeTree(),!0):this.withContext(()=>{var n;if(typeof e=="number"){let r=Date.now()+e;e=()=>Date.now()>r}for(this.parse||(this.parse=this.startParse()),t!=null&&(this.parse.stoppedAt==null||this.parse.stoppedAt>t)&&t<this.state.doc.length&&this.parse.stopAt(t);;){let r=this.parse.advance();if(r)if(this.fragments=this.withoutTempSkipped(vi.addTree(r,this.fragments,this.parse.stoppedAt!=null)),this.treeLen=(n=this.parse.stoppedAt)!==null&&n!==void 0?n:this.state.doc.length,this.tree=r,this.parse=null,this.treeLen<(t??this.state.doc.length))this.parse=this.startParse();else return!0;if(e())return!1}})}takeTree(){let e,t;this.parse&&(e=this.parse.parsedPos)>=this.treeLen&&((this.parse.stoppedAt==null||this.parse.stoppedAt>e)&&this.parse.stopAt(e),this.withContext(()=>{for(;!(t=this.parse.advance()););}),this.treeLen=e,this.tree=t,this.fragments=this.withoutTempSkipped(vi.addTree(this.tree,this.fragments,!0)),this.parse=null)}withContext(e){let t=$o;$o=this;try{return e()}finally{$o=t}}withoutTempSkipped(e){for(let t;t=this.tempSkipped.pop();)e=gb(e,t.from,t.to);return e}changes(e,t){let{fragments:n,tree:r,treeLen:s,viewport:o,skipped:l}=this;if(this.takeTree(),!e.empty){let a=[];if(e.iterChangedRanges((c,h,f,u)=>a.push({fromA:c,toA:h,fromB:f,toB:u})),n=vi.applyChanges(n,a),r=J.empty,s=0,o={from:e.mapPos(o.from,-1),to:e.mapPos(o.to,1)},this.skipped.length){l=[];for(let c of this.skipped){let h=e.mapPos(c.from,1),f=e.mapPos(c.to,-1);h<f&&l.push({from:h,to:f})}}}return new i(this.parser,t,n,r,s,o,l,this.scheduleOn)}updateViewport(e){if(this.viewport.from==e.from&&this.viewport.to==e.to)return!1;this.viewport=e;let t=this.skipped.length;for(let n=0;n<this.skipped.length;n++){let{from:r,to:s}=this.skipped[n];r<e.to&&s>e.from&&(this.fragments=gb(this.fragments,r,s),this.skipped.splice(n--,1))}return this.skipped.length>=t?!1:(this.reset(),!0)}reset(){this.parse&&(this.takeTree(),this.parse=null)}skipUntilInView(e,t){this.skipped.push({from:e,to:t})}static getSkippingParser(e){return new class extends Ni{createParse(t,n,r){let s=r[0].from,o=r[r.length-1].to;return{parsedPos:s,advance(){let a=$o;if(a){for(let c of r)a.tempSkipped.push(c);e&&(a.scheduleOn=a.scheduleOn?Promise.all([a.scheduleOn,e]):e)}return this.parsedPos=o,new J(Oe.none,[],[],o-s)},stoppedAt:null,stopAt(){}}}}}isDone(e){e=Math.min(e,this.state.doc.length);let t=this.fragments;return this.treeLen>=e&&t.length&&t[0].from==0&&t[0].to>=e}static get(){return $o}};function gb(i,e,t){return vi.applyChanges(i,[{fromA:e,toA:t,fromB:e,toB:t}])}var To=class i{constructor(e){this.context=e,this.tree=e.tree}apply(e){if(!e.docChanged&&this.tree==this.context.tree)return this;let t=this.context.changes(e.changes,e.state),n=this.context.treeLen==e.startState.doc.length?void 0:Math.max(e.changes.mapPos(this.context.treeLen),t.viewport.to);return t.work(20,n)||t.takeTree(),new i(t)}static init(e){let t=Math.min(3e3,e.doc.length),n=Co.create(e.facet(Ki).parser,e,{from:0,to:t});return n.work(20,t)||n.takeTree(),new i(n)}};tt.state=de.define({create:To.init,update(i,e){for(let t of e.effects)if(t.is(tt.setState))return t.value;return e.startState.facet(Ki)!=e.state.facet(Ki)?To.init(e.state):i.apply(e)}});var kb=i=>{let e=setTimeout(()=>i(),500);return()=>clearTimeout(e)};typeof requestIdleCallback<"u"&&(kb=i=>{let e=-1,t=setTimeout(()=>{e=requestIdleCallback(i,{timeout:400})},100);return()=>e<0?clearTimeout(t):cancelIdleCallback(e)});var Qd=typeof navigator<"u"&&(!((kd=navigator.scheduling)===null||kd===void 0)&&kd.isInputPending)?()=>navigator.scheduling.isInputPending():null,tC=se.fromClass(class{constructor(e){this.view=e,this.working=null,this.workScheduled=0,this.chunkEnd=-1,this.chunkBudget=-1,this.work=this.work.bind(this),this.scheduleWork()}update(e){let t=this.view.state.field(tt.state).context;(t.updateViewport(e.view.viewport)||this.view.viewport.to>t.treeLen)&&this.scheduleWork(),(e.docChanged||e.selectionSet)&&(this.view.hasFocus&&(this.chunkBudget+=50),this.scheduleWork()),this.checkAsyncSchedule(t)}scheduleWork(){if(this.working)return;let{state:e}=this.view,t=e.field(tt.state);(t.tree!=t.context.tree||!t.context.isDone(e.doc.length))&&(this.working=kb(this.work))}work(e){this.working=null;let t=Date.now();if(this.chunkEnd<t&&(this.chunkEnd<0||this.view.hasFocus)&&(this.chunkEnd=t+3e4,this.chunkBudget=3e3),this.chunkBudget<=0)return;let{state:n,viewport:{to:r}}=this.view,s=n.field(tt.state);if(s.tree==s.context.tree&&s.context.isDone(r+1e5))return;let o=Date.now()+Math.min(this.chunkBudget,100,e&&!Qd?Math.max(25,e.timeRemaining()-5):1e9),l=s.context.treeLen<r&&n.doc.length>r+1e3,a=s.context.work(()=>Qd&&Qd()||Date.now()>o,r+(l?0:1e5));this.chunkBudget-=Date.now()-t,(a||this.chunkBudget<=0)&&(s.context.takeTree(),this.view.dispatch({effects:tt.setState.of(new To(s.context))})),this.chunkBudget>0&&!(a&&!l)&&this.scheduleWork(),this.checkAsyncSchedule(s.context)}checkAsyncSchedule(e){e.scheduleOn&&(this.workScheduled++,e.scheduleOn.then(()=>this.scheduleWork()).catch(t=>_e(this.view.state,t)).then(()=>this.workScheduled--),e.scheduleOn=null)}destroy(){this.working&&this.working()}isWorking(){return!!(this.working||this.workScheduled>0)}},{eventHandlers:{focus(){this.scheduleWork()}}}),Ki=R.define({combine(i){return i.length?i[0]:null},enables:i=>[tt.state,tC,M.contentAttributes.compute([i],e=>{let t=e.facet(i);return t&&t.name?{"data-language":t.name}:{}})]}),Yt=class{constructor(e,t=[]){this.language=e,this.support=t,this.extension=[e,t]}},Ao=class i{constructor(e,t,n,r,s,o=void 0){this.name=e,this.alias=t,this.extensions=n,this.filename=r,this.loadFunc=s,this.support=o,this.loading=null}load(){return this.loading||(this.loading=this.loadFunc().then(e=>this.support=e,e=>{throw this.loading=null,e}))}static of(e){let{load:t,support:n}=e;if(!t){if(!n)throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");t=()=>Promise.resolve(n)}return new i(e.name,(e.alias||[]).concat(e.name).map(r=>r.toLowerCase()),e.extensions||[],e.filename,t,n)}static matchFilename(e,t){for(let r of e)if(r.filename&&r.filename.test(t))return r;let n=/\.([^.]+)$/.exec(t);if(n){for(let r of e)if(r.extensions.indexOf(n[1])>-1)return r}return null}static matchLanguageName(e,t,n=!0){t=t.toLowerCase();for(let r of e)if(r.alias.some(s=>s==t))return r;if(n)for(let r of e)for(let s of r.alias){let o=t.indexOf(s);if(o>-1&&(s.length>2||!/\w/.test(t[o-1])&&!/\w/.test(t[o+s.length])))return r}return null}},iC=R.define(),Ji=R.define({combine:i=>{if(!i.length)return" ";let e=i[0];if(!e||/\S/.test(e)||Array.from(e).some(t=>t!=e[0]))throw new Error("Invalid indent unit: "+JSON.stringify(i[0]));return e}});function Ro(i){let e=i.facet(Ji);return e.charCodeAt(0)==9?i.tabSize*e.length:e.length}function ss(i,e){let t="",n=i.tabSize,r=i.facet(Ji)[0];if(r==" "){for(;e>=n;)t+=" ",e-=n;r=" "}for(let s=0;s<e;s++)t+=r;return t}function yc(i,e){i instanceof ne&&(i=new qn(i));for(let n of i.state.facet(iC)){let r=n(i,e);if(r!==void 0)return r}let t=ee(i.state);return t.length>=e?nC(i,t,e):null}var qn=class{constructor(e,t={}){this.state=e,this.options=t,this.unit=Ro(e)}lineAt(e,t=1){let n=this.state.doc.lineAt(e),{simulateBreak:r,simulateDoubleBreak:s}=this.options;return r!=null&&r>=n.from&&r<=n.to?s&&r==e?{text:"",from:e}:(t<0?r<e:r<=e)?{text:n.text.slice(r-n.from),from:r}:{text:n.text.slice(0,r-n.from),from:n.from}:n}textAfterPos(e,t=1){if(this.options.simulateDoubleBreak&&e==this.options.simulateBreak)return"";let{text:n,from:r}=this.lineAt(e,t);return n.slice(e-r,Math.min(n.length,e+100-r))}column(e,t=1){let{text:n,from:r}=this.lineAt(e,t),s=this.countColumn(n,e-r),o=this.options.overrideIndentation?this.options.overrideIndentation(r):-1;return o>-1&&(s+=o-this.countColumn(n,n.search(/\S|$/))),s}countColumn(e,t=e.length){return Ke(e,this.state.tabSize,t)}lineIndent(e,t=1){let{text:n,from:r}=this.lineAt(e,t),s=this.options.overrideIndentation;if(s){let o=s(r);if(o>-1)return o}return this.countColumn(n,n.search(/\S|$/))}get simulatedBreak(){return this.options.simulateBreak||null}},$i=new U;function nC(i,e,t){let n=e.resolveStack(t),r=e.resolveInner(t,-1).resolve(t,0).enterUnfinishedNodesBefore(t);if(r!=n.node){let s=[];for(let o=r;o&&!(o.from<n.node.from||o.to>n.node.to||o.from==n.node.from&&o.type==n.node.type);o=o.parent)s.push(o);for(let o=s.length-1;o>=0;o--)n={node:s[o],next:n}}return Qb(n,i,t)}function Qb(i,e,t){for(let n=i;n;n=n.next){let r=sC(n.node);if(r)return r(Cd.create(e,t,n))}return 0}function rC(i){return i.pos==i.options.simulateBreak&&i.options.simulateDoubleBreak}function sC(i){let e=i.type.prop($i);if(e)return e;let t=i.firstChild,n;if(t&&(n=t.type.prop(U.closedBy))){let r=i.lastChild,s=r&&n.indexOf(r.name)>-1;return o=>$b(o,!0,1,void 0,s&&!rC(o)?r.from:void 0)}return i.parent==null?oC:null}function oC(){return 0}var Cd=class i extends qn{constructor(e,t,n){super(e.state,e.options),this.base=e,this.pos=t,this.context=n}get node(){return this.context.node}static create(e,t,n){return new i(e,t,n)}get textAfter(){return this.textAfterPos(this.pos)}get baseIndent(){return this.baseIndentFor(this.node)}baseIndentFor(e){let t=this.state.doc.lineAt(e.from);for(;;){let n=e.resolve(t.from);for(;n.parent&&n.parent.from==n.from;)n=n.parent;if(lC(n,e))break;t=this.state.doc.lineAt(n.from)}return this.lineIndent(t.from)}continue(){return Qb(this.context.next,this.base,this.pos)}};function lC(i,e){for(let t=e;t;t=t.parent)if(i==t)return!0;return!1}function aC(i){let e=i.node,t=e.childAfter(e.from),n=e.lastChild;if(!t)return null;let r=i.options.simulateBreak,s=i.state.doc.lineAt(t.from),o=r==null||r<=s.from?s.to:Math.min(s.to,r);for(let l=t.to;;){let a=e.childAfter(l);if(!a||a==n)return null;if(!a.type.isSkipped){if(a.from>=o)return null;let c=/^ */.exec(s.text.slice(t.to-s.from))[0].length;return{from:t.from,to:t.to+c}}l=a.to}}function vb({closing:i,align:e=!0,units:t=1}){return n=>$b(n,e,t,i)}function $b(i,e,t,n,r){let s=i.textAfter,o=s.match(/^\s*/)[0].length,l=n&&s.slice(o,o+n.length)==n||r==i.pos+o,a=e?aC(i):null;return a?l?i.column(a.from):i.column(a.to):i.baseIndent+(l?0:i.unit*t)}var Pb=i=>i.baseIndent;function os({except:i,units:e=1}={}){return t=>{let n=i&&i.test(t.textAfter);return t.baseIndent+(n?0:e*t.unit)}}var cC=200;function Cb(){return ne.transactionFilter.of(i=>{if(!i.docChanged||!i.isUserEvent("input.type")&&!i.isUserEvent("input.complete"))return i;let e=i.startState.languageDataAt("indentOnInput",i.startState.selection.main.head);if(!e.length)return i;let t=i.newDoc,{head:n}=i.newSelection.main,r=t.lineAt(n);if(n>r.from+cC)return i;let s=t.sliceString(r.from,n);if(!e.some(c=>c.test(s)))return i;let{state:o}=i,l=-1,a=[];for(let{head:c}of o.selection.ranges){let h=o.doc.lineAt(c);if(h.from==l)continue;l=h.from;let f=yc(o,h.from);if(f==null)continue;let u=/^\s*/.exec(h.text)[0],d=ss(o,f);u!=d&&a.push({from:h.from,to:h.from+u.length,insert:d})}return a.length?[i,{changes:a,sequential:!0}]:i})}var Ed=R.define(),fi=new U;function Sc(i){let e=i.firstChild,t=i.lastChild;return e&&e.to<t.from?{from:e.to,to:t.type.isError?i.to:t.from}:null}function hC(i,e,t){let n=ee(i);if(n.length<t)return null;let r=n.resolveStack(t,1),s=null;for(let o=r;o;o=o.next){let l=o.node;if(l.to<=t||l.from>t)continue;if(s&&l.from<e)break;let a=l.type.prop(fi);if(a&&(l.to<n.length-50||n.length==i.doc.length||!fC(l))){let c=a(l,i);c&&c.from<=t&&c.from>=e&&c.to>t&&(s=c)}}return s}function fC(i){let e=i.lastChild;return e&&e.to==i.to&&e.type.isError}function Oc(i,e,t){for(let n of i.facet(Ed)){let r=n(i,e,t);if(r)return r}return hC(i,e,t)}function Tb(i,e){let t=e.mapPos(i.from,1),n=e.mapPos(i.to,-1);return t>=n?void 0:{from:t,to:n}}var xc=Y.define({map:Tb}),Xo=Y.define({map:Tb});function Ab(i){let e=[];for(let{head:t}of i.state.selection.ranges)e.some(n=>n.from<=t&&n.to>=t)||e.push(i.lineBlockAt(t));return e}var Gn=de.define({create(){return L.none},update(i,e){e.isUserEvent("delete")&&e.changes.iterChangedRanges((n,r)=>i=mb(i,n,r)),i=i.map(e.changes);let t=[];for(let n of e.effects)n.is(xc)&&!uC(i,n.value.from,n.value.to)?t.push(n.value):n.is(Xo)&&(i=i.update({filter:(r,s)=>n.value.from!=r||n.value.to!=s,filterFrom:n.value.from,filterTo:n.value.to}));if(t.length){let{preparePlaceholder:n}=e.state.facet(Zd),r=t.map(s=>(n?L.replace({widget:new Td(n(e.state,s))}):bb).range(s.from,s.to));i=i.update({add:r})}return e.selection&&(i=mb(i,e.selection.main.head)),i},provide:i=>M.decorations.from(i),toJSON(i,e){let t=[];return i.between(0,e.doc.length,(n,r)=>{t.push(n,r)}),t},fromJSON(i){if(!Array.isArray(i)||i.length%2)throw new RangeError("Invalid JSON for fold state");let e=[];for(let t=0;t<i.length;){let n=i[t++],r=i[t++];if(typeof n!="number"||typeof r!="number")throw new RangeError("Invalid JSON for fold state");e.push(bb.range(n,r))}return L.set(e,!0)}});function mb(i,e,t=e){let n=!1;return i.between(e,t,(r,s)=>{r<t&&s>e&&(n=!0)}),n?i.update({filterFrom:e,filterTo:t,filter:(r,s)=>r>=t||s<=e}):i}function gc(i,e,t){var n;let r=null;return(n=i.field(Gn,!1))===null||n===void 0||n.between(e,t,(s,o)=>{(!r||r.from>s)&&(r={from:s,to:o})}),r}function uC(i,e,t){let n=!1;return i.between(e,e,(r,s)=>{r==e&&s==t&&(n=!0)}),n}function Mb(i,e){return i.field(Gn,!1)?e:e.concat(Y.appendConfig.of(Eb()))}var dC=i=>{for(let e of Ab(i)){let t=Oc(i.state,e.from,e.to);if(t)return i.dispatch({effects:Mb(i.state,[xc.of(t),Rb(i,t)])}),!0}return!1},pC=i=>{if(!i.state.field(Gn,!1))return!1;let e=[];for(let t of Ab(i)){let n=gc(i.state,t.from,t.to);n&&e.push(Xo.of(n),Rb(i,n,!1))}return e.length&&i.dispatch({effects:e}),e.length>0};function Rb(i,e,t=!0){let n=i.state.doc.lineAt(e.from).number,r=i.state.doc.lineAt(e.to).number;return M.announce.of(`${i.state.phrase(t?"Folded lines":"Unfolded lines")} ${n} ${i.state.phrase("to")} ${r}.`)}var OC=i=>{let{state:e}=i,t=[];for(let n=0;n<e.doc.length;){let r=i.lineBlockAt(n),s=Oc(e,r.from,r.to);s&&t.push(xc.of(s)),n=(s?i.lineBlockAt(s.to):r).to+1}return t.length&&i.dispatch({effects:Mb(i.state,t)}),!!t.length},gC=i=>{let e=i.state.field(Gn,!1);if(!e||!e.size)return!1;let t=[];return e.between(0,i.state.doc.length,(n,r)=>{t.push(Xo.of({from:n,to:r}))}),i.dispatch({effects:t}),!0};var Xb=[{key:"Ctrl-Shift-[",mac:"Cmd-Alt-[",run:dC},{key:"Ctrl-Shift-]",mac:"Cmd-Alt-]",run:pC},{key:"Ctrl-Alt-[",run:OC},{key:"Ctrl-Alt-]",run:gC}],mC={placeholderDOM:null,preparePlaceholder:null,placeholderText:"\u2026"},Zd=R.define({combine(i){return Ye(i,mC)}});function Eb(i){let e=[Gn,yC];return i&&e.push(Zd.of(i)),e}function Zb(i,e){let{state:t}=i,n=t.facet(Zd),r=o=>{let l=i.lineBlockAt(i.posAtDOM(o.target)),a=gc(i.state,l.from,l.to);a&&i.dispatch({effects:Xo.of(a)}),o.preventDefault()};if(n.placeholderDOM)return n.placeholderDOM(i,r,e);let s=document.createElement("span");return s.textContent=n.placeholderText,s.setAttribute("aria-label",t.phrase("folded code")),s.title=t.phrase("unfold"),s.className="cm-foldPlaceholder",s.onclick=r,s}var bb=L.replace({widget:new class extends De{toDOM(i){return Zb(i,null)}}}),Td=class extends De{constructor(e){super(),this.value=e}eq(e){return this.value==e.value}toDOM(e){return Zb(e,this.value)}},bC={openText:"\u2304",closedText:"\u203A",markerDOM:null,domEventHandlers:{},foldingChanged:()=>!1},Po=class extends yt{constructor(e,t){super(),this.config=e,this.open=t}eq(e){return this.config==e.config&&this.open==e.open}toDOM(e){if(this.config.markerDOM)return this.config.markerDOM(this.open);let t=document.createElement("span");return t.textContent=this.open?this.config.openText:this.config.closedText,t.title=e.state.phrase(this.open?"Fold line":"Unfold line"),t}};function _b(i={}){let e={...bC,...i},t=new Po(e,!0),n=new Po(e,!1),r=se.fromClass(class{constructor(o){this.from=o.viewport.from,this.markers=this.buildMarkers(o)}update(o){(o.docChanged||o.viewportChanged||o.startState.facet(Ki)!=o.state.facet(Ki)||o.startState.field(Gn,!1)!=o.state.field(Gn,!1)||ee(o.startState)!=ee(o.state)||e.foldingChanged(o))&&(this.markers=this.buildMarkers(o.view))}buildMarkers(o){let l=new lt;for(let a of o.viewportLineBlocks){let c=gc(o.state,a.from,a.to)?n:Oc(o.state,a.from,a.to)?t:null;c&&l.add(a.from,a.from,c)}return l.finish()}}),{domEventHandlers:s}=e;return[r,id({class:"cm-foldGutter",markers(o){var l;return((l=o.plugin(r))===null||l===void 0?void 0:l.markers)||G.empty},initialSpacer(){return new Po(e,!1)},domEventHandlers:{...s,click:(o,l,a)=>{if(s.click&&s.click(o,l,a))return!0;let c=gc(o.state,l.from,l.to);if(c)return o.dispatch({effects:Xo.of(c)}),!0;let h=Oc(o.state,l.from,l.to);return h?(o.dispatch({effects:xc.of(h)}),!0):!1}}}),Eb()]}var yC=M.baseTheme({".cm-foldPlaceholder":{backgroundColor:"#eee",border:"1px solid #ddd",color:"#888",borderRadius:".2em",margin:"0 1px",padding:"0 1px",cursor:"pointer"},".cm-foldGutter span":{padding:"0 1px",cursor:"pointer"}}),mc=class i{constructor(e,t){this.specs=e;let n;function r(l){let a=Tt.newName();return(n||(n=Object.create(null)))["."+a]=l,a}let s=typeof t.all=="string"?t.all:t.all?r(t.all):void 0,o=t.scope;this.scope=o instanceof tt?l=>l.prop(Fi)==o.data:o?l=>l==o:void 0,this.style=wd(e.map(l=>({tag:l.tag,class:l.class||r(Object.assign({},l,{tag:null}))})),{all:s}).style,this.module=n?new Tt(n):null,this.themeType=t.themeType}static define(e,t){return new i(e,t||{})}},Ad=R.define(),Lb=R.define({combine(i){return i.length?[i[0]]:null}});function vd(i){let e=i.facet(Ad);return e.length?e:i.facet(Lb)}function Db(i,e){let t=[SC],n;return i instanceof mc&&(i.module&&t.push(M.styleModule.of(i.module)),n=i.themeType),e?.fallback?t.push(Lb.of(i)):n?t.push(Ad.computeN([M.darkTheme],r=>r.facet(M.darkTheme)==(n=="dark")?[i]:[])):t.push(Ad.of(i)),t}var Md=class{constructor(e){this.markCache=Object.create(null),this.tree=ee(e.state),this.decorations=this.buildDeco(e,vd(e.state)),this.decoratedTo=e.viewport.to}update(e){let t=ee(e.state),n=vd(e.state),r=n!=vd(e.startState),{viewport:s}=e.view,o=e.changes.mapPos(this.decoratedTo,1);t.length<s.to&&!r&&t.type==this.tree.type&&o>=s.to?(this.decorations=this.decorations.map(e.changes),this.decoratedTo=o):(t!=this.tree||e.viewportChanged||r)&&(this.tree=t,this.decorations=this.buildDeco(e.view,n),this.decoratedTo=s.to)}buildDeco(e,t){if(!t||!this.tree.length)return L.none;let n=new lt;for(let{from:r,to:s}of e.visibleRanges)pb(this.tree,t,(o,l,a)=>{n.add(o,l,this.markCache[a]||(this.markCache[a]=L.mark({class:a})))},r,s);return n.finish()}},SC=at.high(se.fromClass(Md,{decorations:i=>i.decorations})),jb=mc.define([{tag:m.meta,color:"#404740"},{tag:m.link,textDecoration:"underline"},{tag:m.heading,textDecoration:"underline",fontWeight:"bold"},{tag:m.emphasis,fontStyle:"italic"},{tag:m.strong,fontWeight:"bold"},{tag:m.strikethrough,textDecoration:"line-through"},{tag:m.keyword,color:"#708"},{tag:[m.atom,m.bool,m.url,m.contentSeparator,m.labelName],color:"#219"},{tag:[m.literal,m.inserted],color:"#164"},{tag:[m.string,m.deleted],color:"#a11"},{tag:[m.regexp,m.escape,m.special(m.string)],color:"#e40"},{tag:m.definition(m.variableName),color:"#00f"},{tag:m.local(m.variableName),color:"#30a"},{tag:[m.typeName,m.namespace],color:"#085"},{tag:m.className,color:"#167"},{tag:[m.special(m.variableName),m.macroName],color:"#256"},{tag:m.definition(m.propertyName),color:"#00c"},{tag:m.comment,color:"#940"},{tag:m.invalid,color:"#f00"}]),xC=M.baseTheme({"&.cm-focused .cm-matchingBracket":{backgroundColor:"#328c8252"},"&.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bb555544"}}),Ib=1e4,Ub="()[]{}",Bb=R.define({combine(i){return Ye(i,{afterCursor:!0,brackets:Ub,maxScanDistance:Ib,renderMatch:QC})}}),wC=L.mark({class:"cm-matchingBracket"}),kC=L.mark({class:"cm-nonmatchingBracket"});function QC(i){let e=[],t=i.matched?wC:kC;return e.push(t.range(i.start.from,i.start.to)),i.end&&e.push(t.range(i.end.from,i.end.to)),e}function yb(i){let e=[],t=i.facet(Bb);for(let n of i.selection.ranges){if(!n.empty)continue;let r=Vt(i,n.head,-1,t)||n.head>0&&Vt(i,n.head-1,1,t)||t.afterCursor&&(Vt(i,n.head,1,t)||n.head<i.doc.length&&Vt(i,n.head+1,-1,t));r&&(e=e.concat(t.renderMatch(r,i)))}return L.set(e,!0)}var vC=se.fromClass(class{constructor(i){this.paused=!1,this.decorations=yb(i.state)}update(i){(i.docChanged||i.selectionSet||this.paused)&&(i.view.composing?(this.decorations=this.decorations.map(i.changes),this.paused=!0):(this.decorations=yb(i.state),this.paused=!1))}},{decorations:i=>i.decorations}),$C=[vC,xC];function Vb(i={}){return[Bb.of(i),$C]}var _d=new U;function Rd(i,e,t){let n=i.prop(e<0?U.openedBy:U.closedBy);if(n)return n;if(i.name.length==1){let r=t.indexOf(i.name);if(r>-1&&r%2==(e<0?1:0))return[t[r+e]]}return null}function Xd(i){let e=i.type.prop(_d);return e?e(i.node):i}function Vt(i,e,t,n={}){let r=n.maxScanDistance||Ib,s=n.brackets||Ub,o=ee(i),l=o.resolveInner(e,t);for(let a=l;a;a=a.parent){let c=Rd(a.type,t,s);if(c&&a.from<a.to){let h=Xd(a);if(h&&(t>0?e>=h.from&&e<h.to:e>h.from&&e<=h.to))return PC(i,e,t,a,h,c,s)}}return CC(i,e,t,o,l.type,r,s)}function PC(i,e,t,n,r,s,o){let l=n.parent,a={from:r.from,to:r.to},c=0,h=l?.cursor();if(h&&(t<0?h.childBefore(n.from):h.childAfter(n.to)))do if(t<0?h.to<=n.from:h.from>=n.to){if(c==0&&s.indexOf(h.type.name)>-1&&h.from<h.to){let f=Xd(h);return{start:a,end:f?{from:f.from,to:f.to}:void 0,matched:!0}}else if(Rd(h.type,t,o))c++;else if(Rd(h.type,-t,o)){if(c==0){let f=Xd(h);return{start:a,end:f&&f.from<f.to?{from:f.from,to:f.to}:void 0,matched:!1}}c--}}while(t<0?h.prevSibling():h.nextSibling());return{start:a,matched:!1}}function CC(i,e,t,n,r,s,o){if(t<0?!e:e==i.doc.length)return null;let l=t<0?i.sliceDoc(e-1,e):i.sliceDoc(e,e+1),a=o.indexOf(l);if(a<0||a%2==0!=t>0)return null;let c={from:t<0?e-1:e,to:t>0?e+1:e},h=i.doc.iterRange(e,t>0?i.doc.length:0),f=0;for(let u=0;!h.next().done&&u<=s;){let d=h.value;t<0&&(u+=d.length);let p=e+u*t;for(let O=t>0?0:d.length-1,g=t>0?d.length:-1;O!=g;O+=t){let b=o.indexOf(d[O]);if(!(b<0||n.resolveInner(p+O,1).type!=r))if(b%2==0==t>0)f++;else{if(f==1)return{start:c,end:{from:p+O,to:p+O+1},matched:b>>1==a>>1};f--}}t>0&&(u+=d.length)}return h.done?{start:c,matched:!1}:null}var TC=Object.create(null),Sb=[Oe.none];var xb=[],wb=Object.create(null),AC=Object.create(null);for(let[i,e]of[["variable","variableName"],["variable-2","variableName.special"],["string-2","string.special"],["def","variableName.definition"],["tag","tagName"],["attribute","attributeName"],["type","typeName"],["builtin","variableName.standard"],["qualifier","modifier"],["error","invalid"],["header","heading"],["property","propertyName"]])AC[i]=MC(TC,e);function $d(i,e){xb.indexOf(i)>-1||(xb.push(i),console.warn(e))}function MC(i,e){let t=[];for(let l of e.split(" ")){let a=[];for(let c of l.split(".")){let h=i[c]||m[c];h?typeof h=="function"?a.length?a=a.map(h):$d(c,`Modifier ${c} used at start of tag`):a.length?$d(c,`Tag ${c} used as modifier`):a=Array.isArray(h)?h:[h]:$d(c,`Unknown highlighting tag ${c}`)}for(let c of a)t.push(c)}if(!t.length)return 0;let n=e.replace(/ /g,"_"),r=n+" "+t.map(l=>l.id),s=wb[r];if(s)return s.id;let o=wb[r]=Oe.define({id:Sb.length,name:n,props:[Bt({[n]:t})]});return Sb.push(o),o.id}var YX={rtl:L.mark({class:"cm-iso",inclusive:!0,attributes:{dir:"rtl"},bidiIsolate:re.RTL}),ltr:L.mark({class:"cm-iso",inclusive:!0,attributes:{dir:"ltr"},bidiIsolate:re.LTR}),auto:L.mark({class:"cm-iso",inclusive:!0,attributes:{dir:"auto"},bidiIsolate:null})};var RC=i=>{let{state:e}=i,t=e.doc.lineAt(e.selection.main.from),n=Vd(i.state,t.from);return n.line?XC(i):n.block?ZC(i):!1};function Bd(i,e){return({state:t,dispatch:n})=>{if(t.readOnly)return!1;let r=i(e,t);return r?(n(t.update(r)),!0):!1}}var XC=Bd(DC,0);var EC=Bd(Kb,0);var ZC=Bd((i,e)=>Kb(i,e,LC(e)),0);function Vd(i,e){let t=i.languageDataAt("commentTokens",e,1);return t.length?t[0]:{}}var Eo=50;function _C(i,{open:e,close:t},n,r){let s=i.sliceDoc(n-Eo,n),o=i.sliceDoc(r,r+Eo),l=/\s*$/.exec(s)[0].length,a=/^\s*/.exec(o)[0].length,c=s.length-l;if(s.slice(c-e.length,c)==e&&o.slice(a,a+t.length)==t)return{open:{pos:n-l,margin:l&&1},close:{pos:r+a,margin:a&&1}};let h,f;r-n<=2*Eo?h=f=i.sliceDoc(n,r):(h=i.sliceDoc(n,n+Eo),f=i.sliceDoc(r-Eo,r));let u=/^\s*/.exec(h)[0].length,d=/\s*$/.exec(f)[0].length,p=f.length-d-t.length;return h.slice(u,u+e.length)==e&&f.slice(p,p+t.length)==t?{open:{pos:n+u+e.length,margin:/\s/.test(h.charAt(u+e.length))?1:0},close:{pos:r-d-t.length,margin:/\s/.test(f.charAt(p-1))?1:0}}:null}function LC(i){let e=[];for(let t of i.selection.ranges){let n=i.doc.lineAt(t.from),r=t.to<=n.to?n:i.doc.lineAt(t.to);r.from>n.from&&r.from==t.to&&(r=t.to==n.to+1?n:i.doc.lineAt(t.to-1));let s=e.length-1;s>=0&&e[s].to>n.from?e[s].to=r.to:e.push({from:n.from+/^\s*/.exec(n.text)[0].length,to:r.to})}return e}function Kb(i,e,t=e.selection.ranges){let n=t.map(s=>Vd(e,s.from).block);if(!n.every(s=>s))return null;let r=t.map((s,o)=>_C(e,n[o],s.from,s.to));if(i!=2&&!r.every(s=>s))return{changes:e.changes(t.map((s,o)=>r[o]?[]:[{from:s.from,insert:n[o].open+" "},{from:s.to,insert:" "+n[o].close}]))};if(i!=1&&r.some(s=>s)){let s=[];for(let o=0,l;o<r.length;o++)if(l=r[o]){let a=n[o],{open:c,close:h}=l;s.push({from:c.pos-a.open.length,to:c.pos+c.margin},{from:h.pos-h.margin,to:h.pos+a.close.length})}return{changes:s}}return null}function DC(i,e,t=e.selection.ranges){let n=[],r=-1;e:for(let{from:s,to:o}of t){let l=n.length,a=1e9,c;for(let h=s;h<=o;){let f=e.doc.lineAt(h);if(c==null&&(c=Vd(e,f.from).line,!c))continue e;if(f.from>r&&(s==o||o>f.from)){r=f.from;let u=/^\s*/.exec(f.text)[0].length,d=u==f.length,p=f.text.slice(u,u+c.length)==c?u:-1;u<f.text.length&&u<a&&(a=u),n.push({line:f,comment:p,token:c,indent:u,empty:d,single:!1})}h=f.to+1}if(a<1e9)for(let h=l;h<n.length;h++)n[h].indent<n[h].line.text.length&&(n[h].indent=a);n.length==l+1&&(n[l].single=!0)}if(i!=2&&n.some(s=>s.comment<0&&(!s.empty||s.single))){let s=[];for(let{line:l,token:a,indent:c,empty:h,single:f}of n)(f||!h)&&s.push({from:l.from+c,insert:a+" "});let o=e.changes(s);return{changes:o,selection:e.selection.map(o,1)}}else if(i!=1&&n.some(s=>s.comment>=0)){let s=[];for(let{line:o,comment:l,token:a}of n)if(l>=0){let c=o.from+l,h=c+a.length;o.text[h-o.from]==" "&&h++,s.push({from:c,to:h})}return{changes:s}}return null}var Dd=we.define(),jC=we.define(),IC=R.define(),Jb=R.define({combine(i){return Ye(i,{minDepth:100,newGroupDelay:500,joinToEvent:(e,t)=>t},{minDepth:Math.max,newGroupDelay:Math.min,joinToEvent:(e,t)=>(n,r)=>e(n,r)||t(n,r)})}}),ey=de.define({create(){return Fn.empty},update(i,e){let t=e.state.facet(Jb),n=e.annotation(Dd);if(n){let a=zt.fromTransaction(e,n.selection),c=n.side,h=c==0?i.undone:i.done;return a?h=kc(h,h.length,t.minDepth,a):h=ry(h,e.startState.selection),new Fn(c==0?n.rest:h,c==0?h:n.rest)}let r=e.annotation(jC);if((r=="full"||r=="before")&&(i=i.isolate()),e.annotation(xe.addToHistory)===!1)return e.changes.empty?i:i.addMapping(e.changes.desc);let s=zt.fromTransaction(e),o=e.annotation(xe.time),l=e.annotation(xe.userEvent);return s?i=i.addChanges(s,o,l,t,e):e.selection&&(i=i.addSelection(e.startState.selection,o,l,t.newGroupDelay)),(r=="full"||r=="after")&&(i=i.isolate()),i},toJSON(i){return{done:i.done.map(e=>e.toJSON()),undone:i.undone.map(e=>e.toJSON())}},fromJSON(i){return new Fn(i.done.map(zt.fromJSON),i.undone.map(zt.fromJSON))}});function ty(i={}){return[ey,Jb.of(i),M.domEventHandlers({beforeinput(e,t){let n=e.inputType=="historyUndo"?iy:e.inputType=="historyRedo"?jd:null;return n?(e.preventDefault(),n(t)):!1}})]}function Qc(i,e){return function({state:t,dispatch:n}){if(!e&&t.readOnly)return!1;let r=t.field(ey,!1);if(!r)return!1;let s=r.pop(i,t,e);return s?(n(s),!0):!1}}var iy=Qc(0,!1),jd=Qc(1,!1),UC=Qc(0,!0),BC=Qc(1,!0);var zt=class i{constructor(e,t,n,r,s){this.changes=e,this.effects=t,this.mapped=n,this.startSelection=r,this.selectionsAfter=s}setSelAfter(e){return new i(this.changes,this.effects,this.mapped,this.startSelection,e)}toJSON(){var e,t,n;return{changes:(e=this.changes)===null||e===void 0?void 0:e.toJSON(),mapped:(t=this.mapped)===null||t===void 0?void 0:t.toJSON(),startSelection:(n=this.startSelection)===null||n===void 0?void 0:n.toJSON(),selectionsAfter:this.selectionsAfter.map(r=>r.toJSON())}}static fromJSON(e){return new i(e.changes&&He.fromJSON(e.changes),[],e.mapped&&yi.fromJSON(e.mapped),e.startSelection&&v.fromJSON(e.startSelection),e.selectionsAfter.map(v.fromJSON))}static fromTransaction(e,t){let n=Xt;for(let r of e.startState.facet(IC)){let s=r(e);s.length&&(n=n.concat(s))}return!n.length&&e.changes.empty?null:new i(e.changes.invert(e.startState.doc),n,void 0,t||e.startState.selection,Xt)}static selection(e){return new i(void 0,Xt,void 0,void 0,e)}};function kc(i,e,t,n){let r=e+1>t+20?e-t-1:0,s=i.slice(r,e);return s.push(n),s}function VC(i,e){let t=[],n=!1;return i.iterChangedRanges((r,s)=>t.push(r,s)),e.iterChangedRanges((r,s,o,l)=>{for(let a=0;a<t.length;){let c=t[a++],h=t[a++];l>=c&&o<=h&&(n=!0)}}),n}function YC(i,e){return i.ranges.length==e.ranges.length&&i.ranges.filter((t,n)=>t.empty!=e.ranges[n].empty).length===0}function ny(i,e){return i.length?e.length?i.concat(e):i:e}var Xt=[],zC=200;function ry(i,e){if(i.length){let t=i[i.length-1],n=t.selectionsAfter.slice(Math.max(0,t.selectionsAfter.length-zC));return n.length&&n[n.length-1].eq(e)?i:(n.push(e),kc(i,i.length-1,1e9,t.setSelAfter(n)))}else return[zt.selection([e])]}function WC(i){let e=i[i.length-1],t=i.slice();return t[i.length-1]=e.setSelAfter(e.selectionsAfter.slice(0,e.selectionsAfter.length-1)),t}function Ld(i,e){if(!i.length)return i;let t=i.length,n=Xt;for(;t;){let r=NC(i[t-1],e,n);if(r.changes&&!r.changes.empty||r.effects.length){let s=i.slice(0,t);return s[t-1]=r,s}else e=r.mapped,t--,n=r.selectionsAfter}return n.length?[zt.selection(n)]:Xt}function NC(i,e,t){let n=ny(i.selectionsAfter.length?i.selectionsAfter.map(l=>l.map(e)):Xt,t);if(!i.changes)return zt.selection(n);let r=i.changes.map(e),s=e.mapDesc(i.changes,!0),o=i.mapped?i.mapped.composeDesc(s):s;return new zt(r,Y.mapEffects(i.effects,e),o,i.startSelection.map(s),n)}var qC=/^(input\.type|delete)($|\.)/,Fn=class i{constructor(e,t,n=0,r=void 0){this.done=e,this.undone=t,this.prevTime=n,this.prevUserEvent=r}isolate(){return this.prevTime?new i(this.done,this.undone):this}addChanges(e,t,n,r,s){let o=this.done,l=o[o.length-1];return l&&l.changes&&!l.changes.empty&&e.changes&&(!n||qC.test(n))&&(!l.selectionsAfter.length&&t-this.prevTime<r.newGroupDelay&&r.joinToEvent(s,VC(l.changes,e.changes))||n=="input.type.compose")?o=kc(o,o.length-1,r.minDepth,new zt(e.changes.compose(l.changes),ny(Y.mapEffects(e.effects,l.changes),l.effects),l.mapped,l.startSelection,Xt)):o=kc(o,o.length,r.minDepth,e),new i(o,Xt,t,n)}addSelection(e,t,n,r){let s=this.done.length?this.done[this.done.length-1].selectionsAfter:Xt;return s.length>0&&t-this.prevTime<r&&n==this.prevUserEvent&&n&&/^select($|\.)/.test(n)&&YC(s[s.length-1],e)?this:new i(ry(this.done,e),this.undone,t,n)}addMapping(e){return new i(Ld(this.done,e),Ld(this.undone,e),this.prevTime,this.prevUserEvent)}pop(e,t,n){let r=e==0?this.done:this.undone;if(r.length==0)return null;let s=r[r.length-1],o=s.selectionsAfter[0]||(s.startSelection?s.startSelection.map(s.changes.invertedDesc,1):t.selection);if(n&&s.selectionsAfter.length)return t.update({selection:s.selectionsAfter[s.selectionsAfter.length-1],annotations:Dd.of({side:e,rest:WC(r),selection:o}),userEvent:e==0?"select.undo":"select.redo",scrollIntoView:!0});if(s.changes){let l=r.length==1?Xt:r.slice(0,r.length-1);return s.mapped&&(l=Ld(l,s.mapped)),t.update({changes:s.changes,selection:s.startSelection,effects:s.effects,annotations:Dd.of({side:e,rest:l,selection:o}),filter:!1,userEvent:e==0?"undo":"redo",scrollIntoView:!0})}else return null}};Fn.empty=new Fn(Xt,Xt);var sy=[{key:"Mod-z",run:iy,preventDefault:!0},{key:"Mod-y",mac:"Mod-Shift-z",run:jd,preventDefault:!0},{linux:"Ctrl-Shift-z",run:jd,preventDefault:!0},{key:"Mod-u",run:UC,preventDefault:!0},{key:"Alt-u",mac:"Mod-Shift-u",run:BC,preventDefault:!0}];function ls(i,e){return v.create(i.ranges.map(e),i.mainIndex)}function Wt(i,e){return i.update({selection:e,scrollIntoView:!0,userEvent:"select"})}function Nt({state:i,dispatch:e},t){let n=ls(i.selection,t);return n.eq(i.selection,!0)?!1:(e(Wt(i,n)),!0)}function vc(i,e){return v.cursor(e?i.to:i.from)}function oy(i,e){return Nt(i,t=>t.empty?i.moveByChar(t,e):vc(t,e))}function ze(i){return i.textDirectionAt(i.state.selection.main.head)==re.LTR}var ly=i=>oy(i,!ze(i)),ay=i=>oy(i,ze(i));function cy(i,e){return Nt(i,t=>t.empty?i.moveByGroup(t,e):vc(t,e))}var GC=i=>cy(i,!ze(i)),FC=i=>cy(i,ze(i));var eE=typeof Intl<"u"&&Intl.Segmenter?new Intl.Segmenter(void 0,{granularity:"word"}):null;function HC(i,e,t){if(e.type.prop(t))return!0;let n=e.to-e.from;return n&&(n>2||/[^\s,.;:]/.test(i.sliceDoc(e.from,e.to)))||e.firstChild}function $c(i,e,t){let n=ee(i).resolveInner(e.head),r=t?U.closedBy:U.openedBy;for(let a=e.head;;){let c=t?n.childAfter(a):n.childBefore(a);if(!c)break;HC(i,c,r)?n=c:a=t?c.to:c.from}let s=n.type.prop(r),o,l;return s&&(o=t?Vt(i,n.from,1):Vt(i,n.to,-1))&&o.matched?l=t?o.end.to:o.end.from:l=t?n.to:n.from,v.cursor(l,t?-1:1)}var KC=i=>Nt(i,e=>$c(i.state,e,!ze(i))),JC=i=>Nt(i,e=>$c(i.state,e,ze(i)));function hy(i,e){return Nt(i,t=>{if(!t.empty)return vc(t,e);let n=i.moveVertically(t,e);return n.head!=t.head?n:i.moveToLineBoundary(t,e)})}var fy=i=>hy(i,!1),uy=i=>hy(i,!0);function dy(i){let e=i.scrollDOM.clientHeight<i.scrollDOM.scrollHeight-2,t=0,n=0,r;if(e){for(let s of i.state.facet(M.scrollMargins)){let o=s(i);o?.top&&(t=Math.max(o?.top,t)),o?.bottom&&(n=Math.max(o?.bottom,n))}r=i.scrollDOM.clientHeight-t-n}else r=(i.dom.ownerDocument.defaultView||window).innerHeight;return{marginTop:t,marginBottom:n,selfScroll:e,height:Math.max(i.defaultLineHeight,r-5)}}function py(i,e){let t=dy(i),{state:n}=i,r=ls(n.selection,o=>o.empty?i.moveVertically(o,e,t.height):vc(o,e));if(r.eq(n.selection))return!1;let s;if(t.selfScroll){let o=i.coordsAtPos(n.selection.main.head),l=i.scrollDOM.getBoundingClientRect(),a=l.top+t.marginTop,c=l.bottom-t.marginBottom;o&&o.top>a&&o.bottom<c&&(s=M.scrollIntoView(r.main.head,{y:"start",yMargin:o.top-a}))}return i.dispatch(Wt(n,r),{effects:s}),!0}var Yb=i=>py(i,!1),Id=i=>py(i,!0);function en(i,e,t){let n=i.lineBlockAt(e.head),r=i.moveToLineBoundary(e,t);if(r.head==e.head&&r.head!=(t?n.to:n.from)&&(r=i.moveToLineBoundary(e,t,!1)),!t&&r.head==n.from&&n.length){let s=/^\s*/.exec(i.state.sliceDoc(n.from,Math.min(n.from+100,n.to)))[0].length;s&&e.head!=n.from+s&&(r=v.cursor(n.from+s))}return r}var eT=i=>Nt(i,e=>en(i,e,!0)),tT=i=>Nt(i,e=>en(i,e,!1)),iT=i=>Nt(i,e=>en(i,e,!ze(i))),nT=i=>Nt(i,e=>en(i,e,ze(i))),rT=i=>Nt(i,e=>i.moveToLineBoundary(e,!1,!1)),sT=i=>Nt(i,e=>i.moveToLineBoundary(e,!0,!1));function oT(i,e,t){let n=!1,r=ls(i.selection,s=>{let o=Vt(i,s.head,-1)||Vt(i,s.head,1)||s.head>0&&Vt(i,s.head-1,1)||s.head<i.doc.length&&Vt(i,s.head+1,-1);if(!o||!o.end)return s;n=!0;let l=o.start.from==s.head?o.end.to:o.end.from;return t?v.range(s.anchor,l):v.cursor(l)});return n?(e(Wt(i,r)),!0):!1}var lT=({state:i,dispatch:e})=>oT(i,e,!1);function Et(i,e,t){let n=ls(i.state.selection,r=>{r.undirectional&&r.head>=r.anchor!=e&&(r=v.range(r.head,r.anchor));let s=t(r);return v.range(r.anchor,s.head,s.goalColumn,s.bidiLevel||void 0,s.assoc)});return n.eq(i.state.selection)?!1:(i.dispatch(Wt(i.state,n)),!0)}function Oy(i,e){return Et(i,e,t=>i.moveByChar(t,e))}var gy=i=>Oy(i,!ze(i)),my=i=>Oy(i,ze(i));function by(i,e){return Et(i,e,t=>i.moveByGroup(t,e))}var aT=i=>by(i,!ze(i)),cT=i=>by(i,ze(i));var hT=i=>{let e=!ze(i);return Et(i,e,t=>$c(i.state,t,e))},fT=i=>{let e=ze(i);return Et(i,e,t=>$c(i.state,t,e))};function yy(i,e){return Et(i,e,t=>i.moveVertically(t,e))}var Sy=i=>yy(i,!1),xy=i=>yy(i,!0);function wy(i,e){return Et(i,e,t=>i.moveVertically(t,e,dy(i).height))}var zb=i=>wy(i,!1),Wb=i=>wy(i,!0),uT=i=>Et(i,!0,e=>en(i,e,!0)),dT=i=>Et(i,!1,e=>en(i,e,!1)),pT=i=>{let e=!ze(i);return Et(i,e,t=>en(i,t,e))},OT=i=>{let e=ze(i);return Et(i,e,t=>en(i,t,e))},gT=i=>Et(i,!1,e=>v.cursor(i.lineBlockAt(e.head).from)),mT=i=>Et(i,!0,e=>v.cursor(i.lineBlockAt(e.head).to)),Nb=({state:i,dispatch:e})=>(e(Wt(i,{anchor:0})),!0),qb=({state:i,dispatch:e})=>(e(Wt(i,{anchor:i.doc.length})),!0),Gb=({state:i,dispatch:e})=>(e(Wt(i,{anchor:i.selection.main.anchor,head:0})),!0),Fb=({state:i,dispatch:e})=>(e(Wt(i,{anchor:i.selection.main.anchor,head:i.doc.length})),!0),bT=({state:i,dispatch:e})=>(e(i.update({selection:{anchor:0,head:i.doc.length},userEvent:"select"})),!0),yT=({state:i,dispatch:e})=>{let t=Pc(i).map(({from:n,to:r})=>v.undirectionalRange(n,Math.min(r+1,i.doc.length)));return e(i.update({selection:v.create(t),userEvent:"select"})),!0},ST=({state:i,dispatch:e})=>{let t=ls(i.selection,n=>{let r=ee(i),s=r.resolveStack(n.from,1);if(n.empty){let o=r.resolveStack(n.from,-1);o.node.from>=s.node.from&&o.node.to<=s.node.to&&(s=o)}for(let o=s;o;o=o.next){let{node:l}=o;if((l.from<n.from&&l.to>=n.to||l.to>n.to&&l.from<=n.from)&&o.next)return v.undirectionalRange(l.from,l.to)}return n});return t.eq(i.selection)?!1:(e(Wt(i,t)),!0)};function ky(i,e){let{state:t}=i,n=t.selection,r=t.selection.ranges.slice();for(let s of t.selection.ranges){let o=t.doc.lineAt(s.head);if(e?o.to<i.state.doc.length:o.from>0)for(let l=s;;){let a=i.moveVertically(l,e);if(a.head<o.from||a.head>o.to){r.some(c=>c.head==a.head)||r.push(a);break}else{if(a.head==l.head)break;l=a}}}return r.length==n.ranges.length?!1:(i.dispatch(Wt(t,v.create(r,r.length-1))),!0)}var xT=i=>ky(i,!1),wT=i=>ky(i,!0),kT=({state:i,dispatch:e})=>{let t=i.selection,n=null;return t.ranges.length>1?n=v.create([t.main]):t.main.empty||(n=v.create([v.cursor(t.main.head)])),n?(e(Wt(i,n)),!0):!1};function Zo(i,e){if(i.state.readOnly)return!1;let t="delete.selection",{state:n}=i,r=n.changeByRange(s=>{let{from:o,to:l}=s;if(o==l){let a=e(s);a<o?(t="delete.backward",a=wc(i,a,!1)):a>o&&(t="delete.forward",a=wc(i,a,!0)),o=Math.min(o,a),l=Math.max(l,a)}else o=wc(i,o,!1),l=wc(i,l,!0);return o==l?{range:s}:{changes:{from:o,to:l},range:v.cursor(o,o<s.head?-1:1)}});return r.changes.empty?!1:(i.dispatch(n.update(r,{scrollIntoView:!0,userEvent:t,effects:t=="delete.selection"?M.announce.of(n.phrase("Selection deleted")):void 0})),!0)}function wc(i,e,t){if(i instanceof M)for(let n of i.state.facet(M.atomicRanges).map(r=>r(i)))n.between(e,e,(r,s)=>{r<e&&s>e&&(e=t?s:r)});return e}var Qy=(i,e,t)=>Zo(i,n=>{let r=n.from,{state:s}=i,o=s.doc.lineAt(r),l,a;if(t&&!e&&r>o.from&&r<o.from+200&&!/[^ \t]/.test(l=o.text.slice(0,r-o.from))){if(l[l.length-1]==" ")return r-1;let c=Ke(l,s.tabSize),h=c%Ro(s)||Ro(s);for(let f=0;f<h&&l[l.length-1-f]==" ";f++)r--;a=r}else a=be(o.text,r-o.from,e,e)+o.from,a==r&&o.number!=(e?s.doc.lines:1)?a+=e?1:-1:!e&&/[\ufe00-\ufe0f]/.test(o.text.slice(a-o.from,r-o.from))&&(a=be(o.text,a-o.from,!1,!1)+o.from);return a}),Ud=i=>Qy(i,!1,!0);var vy=i=>Qy(i,!0,!1),$y=(i,e)=>Zo(i,t=>{let n=t.head,{state:r}=i,s=r.doc.lineAt(n),o=r.charCategorizer(n);for(let l=null;;){if(n==(e?s.to:s.from)){n==t.head&&s.number!=(e?r.doc.lines:1)&&(n+=e?1:-1);break}let a=be(s.text,n-s.from,e)+s.from,c=s.text.slice(Math.min(n,a)-s.from,Math.max(n,a)-s.from),h=o(c);if(l!=null&&h!=l)break;(c!=" "||n!=t.head)&&(l=h),n=a}return n}),Py=i=>$y(i,!1),QT=i=>$y(i,!0);var vT=i=>Zo(i,e=>{let t=i.lineBlockAt(e.head).to;return e.head<t?t:Math.min(i.state.doc.length,e.head+1)});var $T=i=>Zo(i,e=>{let t=i.moveToLineBoundary(e,!1).head;return e.head>t?t:Math.max(0,e.head-1)}),PT=i=>Zo(i,e=>{let t=i.moveToLineBoundary(e,!0).head;return e.head<t?t:Math.min(i.state.doc.length,e.head+1)});var CT=({state:i,dispatch:e})=>{if(i.readOnly)return!1;let t=i.changeByRange(n=>({changes:{from:n.from,to:n.to,insert:q.of(["",""])},range:v.cursor(n.from)}));return e(i.update(t,{scrollIntoView:!0,userEvent:"input"})),!0},TT=({state:i,dispatch:e})=>{if(i.readOnly)return!1;let t=i.changeByRange(n=>{if(!n.empty||n.from==0||n.from==i.doc.length)return{range:n};let r=n.from,s=i.doc.lineAt(r),o=r==s.from?r-1:be(s.text,r-s.from,!1)+s.from,l=r==s.to?r+1:be(s.text,r-s.from,!0)+s.from;return{changes:{from:o,to:l,insert:i.doc.slice(r,l).append(i.doc.slice(o,r))},range:v.cursor(l)}});return t.changes.empty?!1:(e(i.update(t,{scrollIntoView:!0,userEvent:"move.character"})),!0)};function Pc(i){let e=[],t=-1;for(let n of i.selection.ranges){let r=i.doc.lineAt(n.from),s=i.doc.lineAt(n.to);if(!n.empty&&n.to==s.from&&(s=i.doc.lineAt(n.to-1)),t>=r.number){let o=e[e.length-1];o.to=s.to,o.ranges.push(n)}else e.push({from:r.from,to:s.to,ranges:[n]});t=s.number+1}return e}function Cy(i,e,t){if(i.readOnly)return!1;let n=[],r=[];for(let s of Pc(i)){if(t?s.to==i.doc.length:s.from==0)continue;let o=i.doc.lineAt(t?s.to+1:s.from-1),l=o.length+1;if(t){n.push({from:s.to,to:o.to},{from:s.from,insert:o.text+i.lineBreak});for(let a of s.ranges)r.push(v.range(Math.min(i.doc.length,a.anchor+l),Math.min(i.doc.length,a.head+l)))}else{n.push({from:o.from,to:s.from},{from:s.to,insert:i.lineBreak+o.text});for(let a of s.ranges)r.push(v.range(a.anchor-l,a.head-l))}}return n.length?(e(i.update({changes:n,scrollIntoView:!0,selection:v.create(r,i.selection.mainIndex),userEvent:"move.line"})),!0):!1}var AT=({state:i,dispatch:e})=>Cy(i,e,!1),MT=({state:i,dispatch:e})=>Cy(i,e,!0);function Ty(i,e,t){if(i.readOnly)return!1;let n=[];for(let s of Pc(i))t?n.push({from:s.from,insert:i.doc.slice(s.from,s.to)+i.lineBreak}):n.push({from:s.to,insert:i.lineBreak+i.doc.slice(s.from,s.to)});let r=i.changes(n);return e(i.update({changes:r,selection:i.selection.map(r,t?1:-1),scrollIntoView:!0,userEvent:"input.copyline"})),!0}var RT=({state:i,dispatch:e})=>Ty(i,e,!1),XT=({state:i,dispatch:e})=>Ty(i,e,!0),ET=i=>{if(i.state.readOnly)return!1;let{state:e}=i,t=e.changes(Pc(e).map(({from:r,to:s})=>(r>0?r--:s<e.doc.length&&s++,{from:r,to:s}))),n=ls(e.selection,r=>{let s;if(i.lineWrapping){let o=i.lineBlockAt(r.head),l=i.coordsAtPos(r.head,r.assoc||1);l&&(s=o.bottom+i.documentTop-l.bottom+i.defaultLineHeight/2)}return i.moveVertically(r,!0,s)}).map(t);return i.dispatch({changes:t,selection:n,scrollIntoView:!0,userEvent:"delete.line"}),!0};function ZT(i,e){if(/\(\)|\[\]|\{\}/.test(i.sliceDoc(e-1,e+1)))return{from:e,to:e};let t=ee(i).resolveInner(e),n=t.childBefore(e),r=t.childAfter(e),s;return n&&r&&n.to<=e&&r.from>=e&&(s=n.type.prop(U.closedBy))&&s.indexOf(r.name)>-1&&i.doc.lineAt(n.to).from==i.doc.lineAt(r.from).from&&!/\S/.test(i.sliceDoc(n.to,r.from))?{from:n.to,to:r.from}:null}var Hb=Ay(!1),_T=Ay(!0);function Ay(i){return({state:e,dispatch:t})=>{if(e.readOnly)return!1;let n=e.changeByRange(r=>{let{from:s,to:o}=r,l=e.doc.lineAt(s),a=!i&&s==o&&ZT(e,s);i&&(s=o=(o<=l.to?l:e.doc.lineAt(o)).to);let c=new qn(e,{simulateBreak:s,simulateDoubleBreak:!!a}),h=yc(c,s);for(h==null&&(h=Ke(/^\s*/.exec(e.doc.lineAt(s).text)[0],e.tabSize));o<l.to&&/\s/.test(l.text[o-l.from]);)o++;a?{from:s,to:o}=a:s>l.from&&s<l.from+100&&!/\S/.test(l.text.slice(0,s))&&(s=l.from);let f=["",ss(e,h)];return a&&f.push(ss(e,c.lineIndent(l.from,-1))),{changes:{from:s,to:o,insert:q.of(f)},range:v.cursor(s+1+f[1].length)}});return t(e.update(n,{scrollIntoView:!0,userEvent:"input"})),!0}}function Yd(i,e){let t=-1;return i.changeByRange(n=>{let r=[];for(let o=n.from;o<=n.to;){let l=i.doc.lineAt(o);l.number>t&&(n.empty||n.to>l.from)&&(e(l,r,n),t=l.number),o=l.to+1}let s=i.changes(r);return{changes:r,range:v.range(s.mapPos(n.anchor,1),s.mapPos(n.head,1))}})}var LT=({state:i,dispatch:e})=>{if(i.readOnly)return!1;let t=Object.create(null),n=new qn(i,{overrideIndentation:s=>{let o=t[s];return o??-1}}),r=Yd(i,(s,o,l)=>{let a=yc(n,s.from);if(a==null)return;/\S/.test(s.text)||(a=0);let c=/^\s*/.exec(s.text)[0],h=ss(i,a);(c!=h||l.from<s.from+c.length)&&(t[s.from]=a,o.push({from:s.from,to:s.from+c.length,insert:h}))});return r.changes.empty||e(i.update(r,{userEvent:"indent"})),!0},DT=({state:i,dispatch:e})=>i.readOnly?!1:(e(i.update(Yd(i,(t,n)=>{n.push({from:t.from,insert:i.facet(Ji)})}),{userEvent:"input.indent"})),!0),jT=({state:i,dispatch:e})=>i.readOnly?!1:(e(i.update(Yd(i,(t,n)=>{let r=/^\s*/.exec(t.text)[0];if(!r)return;let s=Ke(r,i.tabSize),o=0,l=ss(i,Math.max(0,s-Ro(i)));for(;o<r.length&&o<l.length&&r.charCodeAt(o)==l.charCodeAt(o);)o++;n.push({from:t.from+o,to:t.from+r.length,insert:l.slice(o)})}),{userEvent:"delete.dedent"})),!0),IT=i=>(i.setTabFocusMode(),!0);var UT=[{key:"Ctrl-b",run:ly,shift:gy,preventDefault:!0},{key:"Ctrl-f",run:ay,shift:my},{key:"Ctrl-p",run:fy,shift:Sy},{key:"Ctrl-n",run:uy,shift:xy},{key:"Ctrl-a",run:rT,shift:gT},{key:"Ctrl-e",run:sT,shift:mT},{key:"Ctrl-d",run:vy},{key:"Ctrl-h",run:Ud},{key:"Ctrl-k",run:vT},{key:"Ctrl-Alt-h",run:Py},{key:"Ctrl-o",run:CT},{key:"Ctrl-t",run:TT},{key:"Ctrl-v",run:Id}],BT=[{key:"ArrowLeft",run:ly,shift:gy,preventDefault:!0},{key:"Mod-ArrowLeft",mac:"Alt-ArrowLeft",run:GC,shift:aT,preventDefault:!0},{mac:"Cmd-ArrowLeft",run:iT,shift:pT,preventDefault:!0},{key:"ArrowRight",run:ay,shift:my,preventDefault:!0},{key:"Mod-ArrowRight",mac:"Alt-ArrowRight",run:FC,shift:cT,preventDefault:!0},{mac:"Cmd-ArrowRight",run:nT,shift:OT,preventDefault:!0},{key:"ArrowUp",run:fy,shift:Sy,preventDefault:!0},{mac:"Cmd-ArrowUp",run:Nb,shift:Gb},{mac:"Ctrl-ArrowUp",run:Yb,shift:zb},{key:"ArrowDown",run:uy,shift:xy,preventDefault:!0},{mac:"Cmd-ArrowDown",run:qb,shift:Fb},{mac:"Ctrl-ArrowDown",run:Id,shift:Wb},{key:"PageUp",run:Yb,shift:zb},{key:"PageDown",run:Id,shift:Wb},{key:"Home",run:tT,shift:dT,preventDefault:!0},{key:"Mod-Home",run:Nb,shift:Gb},{key:"End",run:eT,shift:uT,preventDefault:!0},{key:"Mod-End",run:qb,shift:Fb},{key:"Enter",run:Hb,shift:Hb},{key:"Mod-a",run:bT},{key:"Backspace",run:Ud,shift:Ud,preventDefault:!0},{key:"Delete",run:vy,preventDefault:!0},{key:"Mod-Backspace",mac:"Alt-Backspace",run:Py,preventDefault:!0},{key:"Mod-Delete",mac:"Alt-Delete",run:QT,preventDefault:!0},{mac:"Mod-Backspace",run:$T,preventDefault:!0},{mac:"Mod-Delete",run:PT,preventDefault:!0}].concat(UT.map(i=>({mac:i.key,run:i.run,shift:i.shift}))),My=[{key:"Alt-ArrowLeft",mac:"Ctrl-ArrowLeft",run:KC,shift:hT},{key:"Alt-ArrowRight",mac:"Ctrl-ArrowRight",run:JC,shift:fT},{key:"Alt-ArrowUp",run:AT},{key:"Shift-Alt-ArrowUp",run:RT},{key:"Alt-ArrowDown",run:MT},{key:"Shift-Alt-ArrowDown",run:XT},{key:"Mod-Alt-ArrowUp",run:xT},{key:"Mod-Alt-ArrowDown",run:wT},{key:"Escape",run:kT},{key:"Mod-Enter",run:_T},{key:"Alt-l",mac:"Ctrl-l",run:yT},{key:"Mod-i",run:ST,preventDefault:!0},{key:"Mod-[",run:jT},{key:"Mod-]",run:DT},{key:"Mod-Alt-\\",run:LT},{key:"Shift-Mod-k",run:ET},{key:"Shift-Mod-\\",run:lT},{key:"Mod-/",run:RC},{key:"Alt-A",mac:"Ctrl-A",run:EC},{key:"Ctrl-m",mac:"Shift-Alt-m",run:IT}].concat(BT);var Ry=typeof String.prototype.normalize=="function"?i=>i.normalize("NFKD"):i=>i,nn=class{constructor(e,t,n=0,r=e.length,s,o){this.test=o,this.value={from:0,to:0,precise:!1},this.done=!1,this.matches=[],this.buffer="",this.bufferPos=0,this.iter=e.iterRange(n,r),this.bufferStart=n,this.normalize=s?l=>s(Ry(l)):Ry,this.query=this.normalize(t)}peek(){if(this.bufferPos==this.buffer.length){if(this.bufferStart+=this.buffer.length,this.iter.next(),this.iter.done)return-1;this.bufferPos=0,this.buffer=this.iter.value}return Ze(this.buffer,this.bufferPos)}next(){for(;this.matches.length;)this.matches.pop();return this.nextOverlapping()}nextOverlapping(){for(;;){let e=this.peek();if(e<0)return this.done=!0,this;let t=io(e),n=this.bufferStart+this.bufferPos;this.bufferPos+=mt(e);let r=this.normalize(t);if(r.length)for(let s=0,o=n,l=!0;;s++){let a=r.charCodeAt(s),c=this.match(a,o,l,this.bufferPos+this.bufferStart,s==r.length-1);if(c)return this.value=c,this;if(s==r.length-1)break;l&&s<t.length&&t.charCodeAt(s)==a?o++:l=!1}}}match(e,t,n,r,s){let o=null;for(let l=0;l<this.matches.length;){let a=this.matches[l],c=!1;this.query.charCodeAt(a.index)==e&&(a.index==this.query.length-1?o={from:a.from,to:r,precise:s&&a.precise}:(a.index++,c=!0)),c?l++:this.matches.splice(l,1)}return this.query.charCodeAt(0)==e&&(this.query.length==1?o={from:t,to:r,precise:n&&s}:this.matches.push({from:t,index:1,precise:n})),o&&this.test&&!this.test(o.from,o.to,this.buffer,this.bufferStart)&&(o=null),o}};typeof Symbol<"u"&&(nn.prototype[Symbol.iterator]=function(){return this});var Zy={from:-1,to:-1,match:/.*/.exec(""),precise:!0},Fd="gm"+(/x/.unicode==null?"":"u"),Ac=class{constructor(e,t,n,r=0,s=e.length){if(this.text=e,this.to=s,this.curLine="",this.done=!1,this.value=Zy,/\\[sWDnr]|\n|\r|\[\^/.test(t))return new Rc(e,t,n,r,s);this.re=new RegExp(t,Fd+(n?.ignoreCase?"i":"")),this.test=n?.test,this.iter=e.iter();let o=e.lineAt(r);this.curLineStart=o.from,this.matchPos=Xc(e,r),this.getLine(this.curLineStart)}getLine(e){this.iter.next(e),this.iter.lineBreak?this.curLine="":(this.curLine=this.iter.value,this.curLineStart+this.curLine.length>this.to&&(this.curLine=this.curLine.slice(0,this.to-this.curLineStart)),this.iter.next())}nextLine(){this.curLineStart=this.curLineStart+this.curLine.length+1,this.curLineStart>this.to?this.curLine="":this.getLine(0)}next(){for(let e=this.matchPos-this.curLineStart;;){this.re.lastIndex=e;let t=this.matchPos<=this.to&&this.re.exec(this.curLine);if(t){let n=this.curLineStart+t.index,r=n+t[0].length;if(this.matchPos=Xc(this.text,r+(n==r?1:0)),n==this.curLineStart+this.curLine.length&&this.nextLine(),(n<r||n>this.value.to)&&(!this.test||this.test(n,r,t)))return this.value={from:n,to:r,precise:!0,match:t},this;e=this.matchPos-this.curLineStart}else if(this.curLineStart+this.curLine.length<this.to)this.nextLine(),e=0;else return this.done=!0,this}}},zd=new WeakMap,Mc=class i{constructor(e,t){this.from=e,this.text=t}get to(){return this.from+this.text.length}static get(e,t,n){let r=zd.get(e);if(!r||r.from>=n||r.to<=t){let l=new i(t,e.sliceString(t,n));return zd.set(e,l),l}if(r.from==t&&r.to==n)return r;let{text:s,from:o}=r;return o>t&&(s=e.sliceString(t,o)+s,o=t),r.to<n&&(s+=e.sliceString(r.to,n)),zd.set(e,new i(o,s)),new i(t,s.slice(t-o,n-o))}},Rc=class{constructor(e,t,n,r,s){this.text=e,this.to=s,this.done=!1,this.value=Zy,this.matchPos=Xc(e,r),this.re=new RegExp(t,Fd+(n?.ignoreCase?"i":"")),this.test=n?.test,this.flat=Mc.get(e,r,this.chunkEnd(r+5e3))}chunkEnd(e){return e>=this.to?this.to:this.text.lineAt(e).to}next(){for(;;){let e=this.re.lastIndex=this.matchPos-this.flat.from,t=this.re.exec(this.flat.text);if(t&&!t[0]&&t.index==e&&(this.re.lastIndex=e+1,t=this.re.exec(this.flat.text)),t){let n=this.flat.from+t.index,r=n+t[0].length;if((this.flat.to>=this.to||t.index+t[0].length<=this.flat.text.length-10)&&(!this.test||this.test(n,r,t)))return this.value={from:n,to:r,precise:!0,match:t},this.matchPos=Xc(this.text,r+(n==r?1:0)),this}if(this.flat.to==this.to)return this.done=!0,this;this.flat=Mc.get(this.text,this.flat.from,this.chunkEnd(this.flat.from+this.flat.text.length*2))}}};typeof Symbol<"u"&&(Ac.prototype[Symbol.iterator]=Rc.prototype[Symbol.iterator]=function(){return this});function VT(i){try{return new RegExp(i,Fd),!0}catch{return!1}}function Xc(i,e){if(e>=i.length)return e;let t=i.lineAt(e),n;for(;e<t.to&&(n=t.text.charCodeAt(e-t.from))>=56320&&n<57344;)e++;return e}var YT=i=>{let e=z1(i,"cm-goto-line");if(e){let o=e.dom.querySelector("input[type=text]");return o&&o.select(),!0}let{state:t}=i,n=String(t.doc.lineAt(i.state.selection.main.head).number),{close:r,result:s}=Y1(i,{class:"cm-goto-line",label:t.phrase("Go to line"),input:{type:"text",name:"line",value:n},focus:!0,submitLabel:t.phrase("go")});return s.then(o=>{let l=o&&/^([+-])?(\d+)?(:\d+)?(%)?$/.exec(o.elements.line.value);if(!l){i.dispatch({effects:r});return}let a=t.doc.lineAt(t.selection.main.head),[,c,h,f,u]=l,d=f?+f.slice(1):0,p=h?+h:a.number;if(h&&u){let b=p/100;c&&(b=b*(c=="-"?-1:1)+a.number/t.doc.lines),p=Math.round(t.doc.lines*b)}else h&&c&&(p=p*(c=="-"?-1:1)+a.number);let O=t.doc.line(Math.max(1,Math.min(t.doc.lines,p))),g=v.cursor(O.from+Math.max(0,Math.min(d,O.length)));i.dispatch({effects:[r,M.scrollIntoView(g.from,{y:"center"})],selection:g})}),!0},zT={highlightWordAroundCursor:!1,minSelectionLength:1,maxMatches:100,wholeWords:!1},_y=R.define({combine(i){return Ye(i,zT,{highlightWordAroundCursor:(e,t)=>e||t,minSelectionLength:Math.min,maxMatches:Math.min})}});function Ly(i){let e=[FT,GT];return i&&e.push(_y.of(i)),e}var WT=L.mark({class:"cm-selectionMatch"}),NT=L.mark({class:"cm-selectionMatch cm-selectionMatch-main"});function Xy(i,e,t,n){return(t==0||i(e.sliceDoc(t-1,t))!=ae.Word)&&(n==e.doc.length||i(e.sliceDoc(n,n+1))!=ae.Word)}function qT(i,e,t,n){return i(e.sliceDoc(t,t+1))==ae.Word&&i(e.sliceDoc(n-1,n))==ae.Word}var GT=se.fromClass(class{constructor(i){this.decorations=this.getDeco(i)}update(i){(i.selectionSet||i.docChanged||i.viewportChanged)&&(this.decorations=this.getDeco(i.view))}getDeco(i){let e=i.state.facet(_y),{state:t}=i,n=t.selection;if(n.ranges.length>1)return L.none;let r=n.main,s,o=null;if(r.empty){if(!e.highlightWordAroundCursor)return L.none;let a=t.wordAt(r.head);if(!a)return L.none;o=t.charCategorizer(r.head),s=t.sliceDoc(a.from,a.to)}else{let a=r.to-r.from;if(a<e.minSelectionLength||a>200)return L.none;if(e.wholeWords){if(s=t.sliceDoc(r.from,r.to),o=t.charCategorizer(r.head),!(Xy(o,t,r.from,r.to)&&qT(o,t,r.from,r.to)))return L.none}else if(s=t.sliceDoc(r.from,r.to),!s)return L.none}let l=[];for(let a of i.visibleRanges){let c=new nn(t.doc,s,a.from,a.to);for(;!c.next().done;){let{from:h,to:f}=c.value;if((!o||Xy(o,t,h,f))&&(r.empty&&h<=r.from&&f>=r.to?l.push(NT.range(h,f)):(h>=r.to||f<=r.from)&&l.push(WT.range(h,f)),l.length>e.maxMatches))return L.none}}return L.set(l)}},{decorations:i=>i.decorations}),FT=M.baseTheme({".cm-selectionMatch":{backgroundColor:"#99ff7780"},".cm-searchMatch .cm-selectionMatch":{backgroundColor:"transparent"}}),HT=({state:i,dispatch:e})=>{let{selection:t}=i,n=v.create(t.ranges.map(r=>i.wordAt(r.head)||v.cursor(r.head)),t.mainIndex);return n.eq(t)?!1:(e(i.update({selection:n})),!0)};function KT(i,e){let{main:t,ranges:n}=i.selection,r=i.wordAt(t.head),s=r&&r.from==t.from&&r.to==t.to;for(let o=!1,l=new nn(i.doc,e,n[n.length-1].to);;)if(l.next(),l.done){if(o)return null;l=new nn(i.doc,e,0,Math.max(0,n[n.length-1].from-1)),o=!0}else{if(o&&n.some(a=>a.from==l.value.from))continue;if(s){let a=i.wordAt(l.value.from);if(!a||a.from!=l.value.from||a.to!=l.value.to)continue}return l.value}}var JT=({state:i,dispatch:e})=>{let{ranges:t}=i.selection;if(t.some(s=>s.from===s.to))return HT({state:i,dispatch:e});let n=i.sliceDoc(t[0].from,t[0].to);if(i.selection.ranges.some(s=>i.sliceDoc(s.from,s.to)!=n))return!1;let r=KT(i,n);return r?(e(i.update({selection:i.selection.addRange(v.range(r.from,r.to),!1),effects:M.scrollIntoView(r.to)})),!0):!1},hs=R.define({combine(i){return Ye(i,{top:!1,caseSensitive:!1,literal:!1,regexp:!1,wholeWord:!1,createPanel:e=>new Gd(e),scrollToMatch:e=>M.scrollIntoView(e)})}});var Ec=class{constructor(e){this.search=e.search,this.caseSensitive=!!e.caseSensitive,this.literal=!!e.literal,this.regexp=!!e.regexp,this.replace=e.replace||"",this.valid=!!this.search&&(!this.regexp||VT(this.search)),this.unquoted=this.unquote(this.search),this.wholeWord=!!e.wholeWord,this.test=e.test}unquote(e){return this.literal?e:e.replace(/\\([nrt\\])/g,(t,n)=>n=="n"?` +`:n=="r"?"\r":n=="t"?" ":"\\")}eq(e){return this.search==e.search&&this.replace==e.replace&&this.caseSensitive==e.caseSensitive&&this.regexp==e.regexp&&this.wholeWord==e.wholeWord&&this.test==e.test}create(){return this.regexp?new Nd(this):new Wd(this)}getCursor(e,t=0,n){let r=e.doc?e:ne.create({doc:e});return n==null&&(n=r.doc.length),this.regexp?cs(this,r,t,n):as(this,r,t,n)}},Zc=class{constructor(e){this.spec=e}};function e2(i,e,t){return(n,r,s,o)=>{if(t&&!t(n,r,s,o))return!1;let l=n>=o&&r<=o+s.length?s.slice(n-o,r-o):e.doc.sliceString(n,r);return i(l,e,n,r)}}function as(i,e,t,n){let r;return i.wholeWord&&(r=t2(e.doc,e.charCategorizer(e.selection.main.head))),i.test&&(r=e2(i.test,e,r)),new nn(e.doc,i.unquoted,t,n,i.caseSensitive?void 0:s=>s.toLowerCase(),r)}function t2(i,e){return(t,n,r,s)=>((s>t||s+r.length<n)&&(s=Math.max(0,t-2),r=i.sliceString(s,Math.min(i.length,n+2))),(e(_c(r,t-s))!=ae.Word||e(Lc(r,t-s))!=ae.Word)&&(e(Lc(r,n-s))!=ae.Word||e(_c(r,n-s))!=ae.Word))}var Wd=class extends Zc{constructor(e){super(e)}nextMatch(e,t,n){let r=as(this.spec,e,n,e.doc.length).nextOverlapping();if(r.done){let s=Math.min(e.doc.length,t+this.spec.unquoted.length);r=as(this.spec,e,0,s).nextOverlapping()}return r.done||r.value.from==t&&r.value.to==n?null:r.value}prevMatchInRange(e,t,n){for(let r=n;;){let s=Math.max(t,r-1e4-this.spec.unquoted.length),o=as(this.spec,e,s,r),l=null;for(;!o.nextOverlapping().done;)l=o.value;if(l)return l;if(s==t)return null;r-=1e4}}prevMatch(e,t,n){let r=this.prevMatchInRange(e,0,t);return r||(r=this.prevMatchInRange(e,Math.max(0,n-this.spec.unquoted.length),e.doc.length)),r&&(r.from!=t||r.to!=n)?r:null}getReplacement(e){return this.spec.unquote(this.spec.replace)}matchAll(e,t){let n=as(this.spec,e,0,e.doc.length),r=[];for(;!n.next().done;){if(r.length>=t)return null;r.push(n.value)}return r}highlight(e,t,n,r){let s=as(this.spec,e,Math.max(0,t-this.spec.unquoted.length),Math.min(n+this.spec.unquoted.length,e.doc.length));for(;!s.next().done;)r(s.value.from,s.value.to)}};function i2(i,e,t){return(n,r,s)=>(!t||t(n,r,s))&&i(s[0],e,n,r)}function cs(i,e,t,n){let r;return i.wholeWord&&(r=n2(e.charCategorizer(e.selection.main.head))),i.test&&(r=i2(i.test,e,r)),new Ac(e.doc,i.search,{ignoreCase:!i.caseSensitive,test:r},t,n)}function _c(i,e){return i.slice(be(i,e,!1),e)}function Lc(i,e){return i.slice(e,be(i,e))}function n2(i){return(e,t,n)=>!n[0].length||(i(_c(n.input,n.index))!=ae.Word||i(Lc(n.input,n.index))!=ae.Word)&&(i(Lc(n.input,n.index+n[0].length))!=ae.Word||i(_c(n.input,n.index+n[0].length))!=ae.Word)}var Nd=class extends Zc{nextMatch(e,t,n){let r=cs(this.spec,e,n,e.doc.length).next();return r.done&&(r=cs(this.spec,e,0,t).next()),r.done?null:r.value}prevMatchInRange(e,t,n){for(let r=1;;r++){let s=Math.max(t,n-r*1e4),o=cs(this.spec,e,s,n),l=null;for(;!o.next().done;)l=o.value;if(l&&(s==t||l.from>s+10))return l;if(s==t)return null}}prevMatch(e,t,n){return this.prevMatchInRange(e,0,t)||this.prevMatchInRange(e,n,e.doc.length)}getReplacement(e){return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g,(t,n)=>{if(n=="&")return e.match[0];if(n=="$")return"$";for(let r=n.length;r>0;r--){let s=+n.slice(0,r);if(s>0&&s<e.match.length)return e.match[s]+n.slice(r)}return t})}matchAll(e,t){let n=cs(this.spec,e,0,e.doc.length),r=[];for(;!n.next().done;){if(r.length>=t)return null;r.push(n.value)}return r}highlight(e,t,n,r){let s=cs(this.spec,e,Math.max(0,t-250),Math.min(n+250,e.doc.length));for(;!s.next().done;)r(s.value.from,s.value.to)}},Lo=Y.define(),Hd=Y.define(),tn=de.define({create(i){return new _o(qd(i).create(),null)},update(i,e){for(let t of e.effects)t.is(Lo)?i=new _o(t.value.create(),i.panel):t.is(Hd)&&(i=new _o(i.query,t.value?Kd:null));return i},provide:i=>Vn.from(i,e=>e.panel)});var _o=class{constructor(e,t){this.query=e,this.panel=t}},r2=L.mark({class:"cm-searchMatch"}),s2=L.mark({class:"cm-searchMatch cm-searchMatch-selected"}),o2=se.fromClass(class{constructor(i){this.view=i,this.decorations=this.highlight(i.state.field(tn))}update(i){let e=i.state.field(tn);(e!=i.startState.field(tn)||i.docChanged||i.selectionSet||i.viewportChanged)&&(this.decorations=this.highlight(e))}highlight({query:i,panel:e}){if(!e||!i.spec.valid)return L.none;let{view:t}=this,n=new lt;for(let r=0,s=t.visibleRanges,o=s.length;r<o;r++){let{from:l,to:a}=s[r];for(;r<o-1&&a>s[r+1].from-500;)a=s[++r].to;i.highlight(t.state,l,a,(c,h)=>{let f=t.state.selection.ranges.some(u=>u.from==c&&u.to==h);n.add(c,h,f?s2:r2)})}return n.finish()}},{decorations:i=>i.decorations});function Do(i){return e=>{let t=e.state.field(tn,!1);return t&&t.query.spec.valid?i(e,t):Iy(e)}}var Dc=Do((i,{query:e})=>{let{to:t}=i.state.selection.main,n=e.nextMatch(i.state,t,t);if(!n)return!1;let r=v.single(n.from,n.to),s=i.state.facet(hs);return i.dispatch({selection:r,effects:[Jd(i,n),s.scrollToMatch(r.main,i)],userEvent:"select.search"}),jy(i),!0}),jc=Do((i,{query:e})=>{let{state:t}=i,{from:n}=t.selection.main,r=e.prevMatch(t,n,n);if(!r)return!1;let s=v.single(r.from,r.to),o=i.state.facet(hs);return i.dispatch({selection:s,effects:[Jd(i,r),o.scrollToMatch(s.main,i)],userEvent:"select.search"}),jy(i),!0}),l2=Do((i,{query:e})=>{let t=e.matchAll(i.state,1e3);return!t||!t.length?!1:(i.dispatch({selection:v.create(t.map(n=>v.range(n.from,n.to))),userEvent:"select.search.matches"}),!0)}),a2=({state:i,dispatch:e})=>{let t=i.selection;if(t.ranges.length>1||t.main.empty)return!1;let{from:n,to:r}=t.main,s=[],o=0;for(let l=new nn(i.doc,i.sliceDoc(n,r));!l.next().done;){if(s.length>1e3)return!1;l.value.from==n&&(o=s.length),s.push(v.range(l.value.from,l.value.to))}return e(i.update({selection:v.create(s,o),userEvent:"select.search.matches"})),!0},Ey=Do((i,{query:e})=>{let{state:t}=i,{from:n,to:r}=t.selection.main;if(t.readOnly)return!1;let s=e.nextMatch(t,n,n);if(!s)return!1;let o=s,l=[],a,c,h=[];o.precise?o.from==n&&o.to==r&&(c=t.toText(e.getReplacement(o)),l.push({from:o.from,to:o.to,insert:c}),o=e.nextMatch(t,o.from,o.to),h.push(M.announce.of(t.phrase("replaced match on line $",t.doc.lineAt(n).number)+"."))):o=e.nextMatch(t,o.from,o.to);let f=i.state.changes(l);return o&&(a=v.single(o.from,o.to).map(f),h.push(Jd(i,o)),h.push(t.facet(hs).scrollToMatch(a.main,i))),i.dispatch({changes:f,selection:a,effects:h,userEvent:"input.replace"}),!0}),c2=Do((i,{query:e})=>{if(i.state.readOnly)return!1;let t=[];for(let r of e.matchAll(i.state,1e9)){let{from:s,to:o,precise:l}=r;l&&t.push({from:s,to:o,insert:e.getReplacement(r)})}if(!t.length)return!1;let n=i.state.phrase("replaced $ matches",t.length)+".";return i.dispatch({changes:t,effects:M.announce.of(n),userEvent:"input.replace.all"}),!0});function Kd(i){return i.state.facet(hs).createPanel(i)}function qd(i,e){var t,n,r,s,o;let l=i.selection.main,a=l.empty||l.to>l.from+100?"":i.sliceDoc(l.from,l.to);if(e&&!a)return e;let c=i.facet(hs);return new Ec({search:((t=e?.literal)!==null&&t!==void 0?t:c.literal)?a:a.replace(/\n/g,"\\n"),caseSensitive:(n=e?.caseSensitive)!==null&&n!==void 0?n:c.caseSensitive,literal:(r=e?.literal)!==null&&r!==void 0?r:c.literal,regexp:(s=e?.regexp)!==null&&s!==void 0?s:c.regexp,wholeWord:(o=e?.wholeWord)!==null&&o!==void 0?o:c.wholeWord})}function Dy(i){let e=is(i,Kd);return e&&e.dom.querySelector("[main-field]")}function jy(i){let e=Dy(i);e&&e==i.root.activeElement&&e.select()}var Iy=i=>{let e=i.state.field(tn,!1);if(e&&e.panel){let t=Dy(i);if(t&&t!=i.root.activeElement){let n=qd(i.state,e.query.spec);n.valid&&i.dispatch({effects:Lo.of(n)}),t.focus(),t.select()}}else i.dispatch({effects:[Hd.of(!0),e?Lo.of(qd(i.state,e.query.spec)):Y.appendConfig.of(f2)]});return!0},Uy=i=>{let e=i.state.field(tn,!1);if(!e||!e.panel)return!1;let t=is(i,Kd);return t&&t.dom.contains(i.root.activeElement)&&i.focus(),i.dispatch({effects:Hd.of(!1)}),!0},By=[{key:"Mod-f",run:Iy,scope:"editor search-panel"},{key:"F3",run:Dc,shift:jc,scope:"editor search-panel",preventDefault:!0},{key:"Mod-g",run:Dc,shift:jc,scope:"editor search-panel",preventDefault:!0},{key:"Escape",run:Uy,scope:"editor search-panel"},{key:"Mod-Shift-l",run:a2},{key:"Mod-Alt-g",run:YT},{key:"Mod-d",run:JT,preventDefault:!0}],Gd=class{constructor(e){this.view=e;let t=this.query=e.state.field(tn).query.spec;this.commit=this.commit.bind(this),this.searchField=te("input",{value:t.search,placeholder:xt(e,"Find"),"aria-label":xt(e,"Find"),class:"cm-textfield",name:"search",form:"","main-field":"true",onchange:this.commit,onkeyup:this.commit}),this.replaceField=te("input",{value:t.replace,placeholder:xt(e,"Replace"),"aria-label":xt(e,"Replace"),class:"cm-textfield",name:"replace",form:"",onchange:this.commit,onkeyup:this.commit}),this.caseField=te("input",{type:"checkbox",name:"case",form:"",checked:t.caseSensitive,onchange:this.commit}),this.reField=te("input",{type:"checkbox",name:"re",form:"",checked:t.regexp,onchange:this.commit}),this.wordField=te("input",{type:"checkbox",name:"word",form:"",checked:t.wholeWord,onchange:this.commit});function n(r,s,o){return te("button",{class:"cm-button",name:r,onclick:s,type:"button"},o)}this.dom=te("div",{onkeydown:r=>this.keydown(r),class:"cm-search"},[this.searchField,n("next",()=>Dc(e),[xt(e,"next")]),n("prev",()=>jc(e),[xt(e,"previous")]),n("select",()=>l2(e),[xt(e,"all")]),te("label",null,[this.caseField,xt(e,"match case")]),te("label",null,[this.reField,xt(e,"regexp")]),te("label",null,[this.wordField,xt(e,"by word")]),...e.state.readOnly?[]:[te("br"),this.replaceField,n("replace",()=>Ey(e),[xt(e,"replace")]),n("replaceAll",()=>c2(e),[xt(e,"replace all")])],te("button",{name:"close",onclick:()=>Uy(e),"aria-label":xt(e,"close"),type:"button"},["\xD7"])])}commit(){let e=new Ec({search:this.searchField.value,caseSensitive:this.caseField.checked,regexp:this.reField.checked,wholeWord:this.wordField.checked,replace:this.replaceField.value});e.eq(this.query)||(this.query=e,this.view.dispatch({effects:Lo.of(e)}))}keydown(e){C1(this.view,e,"search-panel")?e.preventDefault():e.keyCode==13&&e.target==this.searchField?(e.preventDefault(),(e.shiftKey?jc:Dc)(this.view)):e.keyCode==13&&e.target==this.replaceField&&(e.preventDefault(),Ey(this.view))}update(e){for(let t of e.transactions)for(let n of t.effects)n.is(Lo)&&!n.value.eq(this.query)&&this.setQuery(n.value)}setQuery(e){this.query=e,this.searchField.value=e.search,this.replaceField.value=e.replace,this.caseField.checked=e.caseSensitive,this.reField.checked=e.regexp,this.wordField.checked=e.wholeWord}mount(){this.searchField.select()}get pos(){return 80}get top(){return this.view.state.facet(hs).top}};function xt(i,e){return i.state.phrase(e)}var Cc=30,Tc=/[\s\.,:;?!]/;function Jd(i,{from:e,to:t}){let n=i.state.doc.lineAt(e),r=i.state.doc.lineAt(t).to,s=Math.max(n.from,e-Cc),o=Math.min(r,t+Cc),l=i.state.sliceDoc(s,o);if(s!=n.from){for(let a=0;a<Cc;a++)if(!Tc.test(l[a+1])&&Tc.test(l[a])){l=l.slice(a);break}}if(o!=r){for(let a=l.length-1;a>l.length-Cc;a--)if(!Tc.test(l[a-1])&&Tc.test(l[a])){l=l.slice(0,a);break}}return M.announce.of(`${i.state.phrase("current match")}. ${l} ${i.state.phrase("on line")} ${n.number}.`)}var h2=M.baseTheme({".cm-panel.cm-search":{padding:"2px 6px 4px",position:"relative","& [name=close]":{position:"absolute",top:"0",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",padding:0,margin:0},"& input, & button, & label":{margin:".2em .6em .2em 0"},"& input[type=checkbox]":{marginRight:".2em"},"& label":{fontSize:"80%",whiteSpace:"pre"}},"&light .cm-searchMatch":{backgroundColor:"#ffff0054"},"&dark .cm-searchMatch":{backgroundColor:"#00ffff8a"},"&light .cm-searchMatch-selected":{backgroundColor:"#ff6a0054"},"&dark .cm-searchMatch-selected":{backgroundColor:"#ff00ff8a"}}),f2=[tn,at.low(o2),h2];var fs=class{constructor(e,t,n,r){this.state=e,this.pos=t,this.explicit=n,this.view=r,this.abortListeners=[],this.abortOnDocChange=!1}tokenBefore(e){let t=ee(this.state).resolveInner(this.pos,-1);for(;t&&e.indexOf(t.name)<0;)t=t.parent;return t?{from:t.from,to:this.pos,text:this.state.sliceDoc(t.from,this.pos),type:t.type}:null}matchBefore(e){let t=this.state.doc.lineAt(this.pos),n=Math.max(t.from,this.pos-250),r=t.text.slice(n-t.from,this.pos-t.from),s=r.search(Ky(e,!1));return s<0?null:{from:n+s,to:this.pos,text:r.slice(s)}}get aborted(){return this.abortListeners==null}addEventListener(e,t,n){e=="abort"&&this.abortListeners&&(this.abortListeners.push(t),n&&n.onDocChange&&(this.abortOnDocChange=!0))}};function Vy(i){let e=Object.keys(i).join(""),t=/\w/.test(e);return t&&(e=e.replace(/\w/g,"")),`[${t?"\\w":""}${e.replace(/[^\w\s]/g,"\\$&")}]`}function u2(i){let e=Object.create(null),t=Object.create(null);for(let{label:r}of i){e[r[0]]=!0;for(let s=1;s<r.length;s++)t[r[s]]=!0}let n=Vy(e)+Vy(t)+"*$";return[new RegExp("^"+n),new RegExp(n)]}function up(i){let e=i.map(r=>typeof r=="string"?{label:r}:r),[t,n]=e.every(r=>/^\w+$/.test(r.label))?[/\w*$/,/\w+$/]:u2(e);return r=>{let s=r.matchBefore(n);return s||r.explicit?{from:s?s.from:r.pos,options:e,validFor:t}:null}}function Hy(i,e){return t=>{for(let n=ee(t.state).resolveInner(t.pos,-1);n;n=n.parent){if(i.indexOf(n.name)>-1)return null;if(n.type.isTop)break}return e(t)}}var Uc=class{constructor(e,t,n,r){this.completion=e,this.source=t,this.match=n,this.score=r}};function Kn(i){return i.selection.main.from}function Ky(i,e){var t;let{source:n}=i,r=e&&n[0]!="^",s=n[n.length-1]!="$";return!r&&!s?i:new RegExp(`${r?"^":""}(?:${n})${s?"$":""}`,(t=i.flags)!==null&&t!==void 0?t:i.ignoreCase?"i":"")}var dp=we.define();function d2(i,e,t,n){let{main:r}=i.selection,s=t-r.from,o=n-r.from;return{...i.changeByRange(l=>{if(l!=r&&t!=n&&i.sliceDoc(l.from+s,l.from+o)!=i.sliceDoc(t,n))return{range:l};let a=i.toText(e);return{changes:{from:l.from+s,to:n==r.from?l.to:l.from+o,insert:a},range:v.cursor(l.from+s+a.length)}}),scrollIntoView:!0,userEvent:"input.complete"}}var Yy=new WeakMap;function p2(i){if(!Array.isArray(i))return i;let e=Yy.get(i);return e||Yy.set(i,e=up(i)),e}var Bc=Y.define(),jo=Y.define(),np=class{constructor(e){this.pattern=e,this.chars=[],this.folded=[],this.any=[],this.precise=[],this.byWord=[],this.score=0,this.matched=[];for(let t=0;t<e.length;){let n=Ze(e,t),r=mt(n);this.chars.push(n);let s=e.slice(t,t+r),o=s.toUpperCase();this.folded.push(Ze(o==s?s.toLowerCase():o,0)),t+=r}this.astral=e.length!=this.chars.length}ret(e,t){return this.score=e,this.matched=t,this}match(e){if(this.pattern.length==0)return this.ret(-100,[]);if(e.length<this.pattern.length)return null;let{chars:t,folded:n,any:r,precise:s,byWord:o}=this;if(t.length==1){let w=Ze(e,0),x=mt(w),X=x==e.length?0:-100;if(w!=t[0])if(w==n[0])X+=-200;else return null;return this.ret(X,[0,x])}let l=e.indexOf(this.pattern);if(l==0)return this.ret(e.length==this.pattern.length?0:-100,[0,this.pattern.length]);let a=t.length,c=0;if(l<0){for(let w=0,x=Math.min(e.length,200);w<x&&c<a;){let X=Ze(e,w);(X==t[c]||X==n[c])&&(r[c++]=w),w+=mt(X)}if(c<a)return null}let h=0,f=0,u=!1,d=0,p=-1,O=-1,g=/[a-z]/.test(e),b=!0;for(let w=0,x=Math.min(e.length,200),X=0;w<x&&f<a;){let C=Ze(e,w);l<0&&(h<a&&C==t[h]&&(s[h++]=w),d<a&&(C==t[d]||C==n[d]?(d==0&&(p=w),O=w+1,d++):d=0));let T,y=C<255?C>=48&&C<=57||C>=97&&C<=122?2:C>=65&&C<=90?1:0:(T=io(C))!=T.toLowerCase()?1:T!=T.toUpperCase()?2:0;(!w||y==1&&g||X==0&&y!=0)&&(t[f]==C||n[f]==C&&(u=!0)?o[f++]=w:o.length&&(b=!1)),X=y,w+=mt(C)}return f==a&&o[0]==0&&b?this.result(-100+(u?-200:0),o,e):d==a&&p==0?this.ret(-200-e.length+(O==e.length?0:-100),[0,O]):l>-1?this.ret(-700-e.length,[l,l+this.pattern.length]):d==a?this.ret(-900-e.length,[p,O]):f==a?this.result(-100+(u?-200:0)+-700+(b?0:-1100),o,e):t.length==2?null:this.result((r[0]?-700:0)+-200+-1100,r,e)}result(e,t,n){let r=[],s=0;for(let o of t){let l=o+(this.astral?mt(Ze(n,o)):1);s&&r[s-1]==o?r[s-1]=l:(r[s++]=o,r[s++]=l)}return this.ret(e-n.length,r)}},rp=class{constructor(e){this.pattern=e,this.matched=[],this.score=0,this.folded=e.toLowerCase()}match(e){if(e.length<this.pattern.length)return null;let t=e.slice(0,this.pattern.length),n=t==this.pattern?0:t.toLowerCase()==this.folded?-200:null;return n==null?null:(this.matched=[0,t.length],this.score=n+(e.length==this.pattern.length?0:-100),this)}},Ae=R.define({combine(i){return Ye(i,{activateOnTyping:!0,activateOnCompletion:()=>!1,activateOnTypingDelay:100,selectOnOpen:!0,override:null,closeOnBlur:!0,maxRenderedOptions:100,defaultKeymap:!0,tooltipClass:()=>"",optionClass:()=>"",aboveCursor:!1,icons:!0,addToOptions:[],positionInfo:O2,filterStrict:!1,compareCompletions:(e,t)=>(e.sortText||e.label).localeCompare(t.sortText||t.label),interactionDelay:75,updateSyncTime:100},{defaultKeymap:(e,t)=>e&&t,closeOnBlur:(e,t)=>e&&t,icons:(e,t)=>e&&t,tooltipClass:(e,t)=>n=>zy(e(n),t(n)),optionClass:(e,t)=>n=>zy(e(n),t(n)),addToOptions:(e,t)=>e.concat(t),filterStrict:(e,t)=>e||t})}});function zy(i,e){return i?e?i+" "+e:i:e}function O2(i,e,t,n,r,s){let o=i.textDirection==re.RTL,l=o,a=!1,c="top",h,f,u=e.left-r.left,d=r.right-e.right,p=n.right-n.left,O=n.bottom-n.top;if(l&&u<Math.min(p,d)?l=!1:!l&&d<Math.min(p,u)&&(l=!0),p<=(l?u:d))h=Math.max(r.top,Math.min(t.top,r.bottom-O))-e.top,f=Math.min(400,l?u:d);else{a=!0,f=Math.min(400,(o?e.right:r.right-e.left)-30);let w=r.bottom-e.bottom;w>=O||w>e.top?h=t.bottom-e.top:(c="bottom",h=e.bottom-t.top)}let g=(e.bottom-e.top)/s.offsetHeight,b=(e.right-e.left)/s.offsetWidth;return{style:`${c}: ${h/g}px; max-width: ${f/b}px`,class:"cm-completionInfo-"+(a?o?"left-narrow":"right-narrow":l?"left":"right")}}var pp=Y.define();function g2(i){let e=i.addToOptions.slice();return i.icons&&e.push({render(t){let n=document.createElement("div");return n.classList.add("cm-completionIcon"),t.type&&n.classList.add(...t.type.split(/\s+/g).map(r=>"cm-completionIcon-"+r)),n.setAttribute("aria-hidden","true"),n},position:20}),e.push({render(t,n,r,s){let o=document.createElement("span");o.className="cm-completionLabel";let l=t.displayLabel||t.label,a=0;for(let c=0;c<s.length;){let h=s[c++],f=s[c++];h>a&&o.appendChild(document.createTextNode(l.slice(a,h)));let u=o.appendChild(document.createElement("span"));u.appendChild(document.createTextNode(l.slice(h,f))),u.className="cm-completionMatchedText",a=f}return a<l.length&&o.appendChild(document.createTextNode(l.slice(a))),o},position:50},{render(t){if(!t.detail)return null;let n=document.createElement("span");return n.className="cm-completionDetail",n.textContent=t.detail,n},position:80}),e.sort((t,n)=>t.position-n.position).map(t=>t.render)}function ep(i,e,t){if(i<=t)return{from:0,to:i};if(e<0&&(e=0),e<=i>>1){let r=Math.floor(e/t);return{from:r*t,to:(r+1)*t}}let n=Math.ceil((i-e)/t);return{from:i-n*t,to:i-(n-1)*t}}var sp=class{constructor(e,t,n){this.view=e,this.stateField=t,this.applyCompletion=n,this.info=null,this.infoDestroy=null,this.placeInfoReq={read:()=>this.measureInfo(),write:a=>this.placeInfo(a),key:this},this.space=null,this.currentClass="";let r=e.state.field(t),{options:s,selected:o}=r.open,l=e.state.facet(Ae);this.optionContent=g2(l),this.optionClass=l.optionClass,this.tooltipClass=l.tooltipClass,this.range=ep(s.length,o,l.maxRenderedOptions),this.dom=document.createElement("div"),this.dom.className="cm-tooltip-autocomplete",this.updateTooltipClass(e.state),this.dom.addEventListener("mousedown",a=>{let{options:c}=e.state.field(t).open;for(let h=a.target,f;h&&h!=this.dom;h=h.parentNode)if(h.nodeName=="LI"&&(f=/-(\d+)$/.exec(h.id))&&+f[1]<c.length){this.applyCompletion(e,c[+f[1]]),a.preventDefault();return}if(a.target==this.list){let h=this.list.classList.contains("cm-completionListIncompleteTop")&&a.clientY<this.list.firstChild.getBoundingClientRect().top?this.range.from-1:this.list.classList.contains("cm-completionListIncompleteBottom")&&a.clientY>this.list.lastChild.getBoundingClientRect().bottom?this.range.to:null;h!=null&&(e.dispatch({effects:pp.of(h)}),a.preventDefault())}}),this.dom.addEventListener("focusout",a=>{let c=e.state.field(this.stateField,!1);c&&c.tooltip&&e.state.facet(Ae).closeOnBlur&&a.relatedTarget!=e.contentDOM&&e.dispatch({effects:jo.of(null)})}),this.showOptions(s,r.id)}mount(){this.updateSel()}showOptions(e,t){this.list&&this.list.remove(),this.list=this.dom.appendChild(this.createListBox(e,t,this.range)),this.list.addEventListener("scroll",()=>{this.info&&this.view.requestMeasure(this.placeInfoReq)})}update(e){var t;let n=e.state.field(this.stateField),r=e.startState.field(this.stateField);if(this.updateTooltipClass(e.state),n!=r){let{options:s,selected:o,disabled:l}=n.open;(!r.open||r.open.options!=s)&&(this.range=ep(s.length,o,e.state.facet(Ae).maxRenderedOptions),this.showOptions(s,n.id)),this.updateSel(),l!=((t=r.open)===null||t===void 0?void 0:t.disabled)&&this.dom.classList.toggle("cm-tooltip-autocomplete-disabled",!!l)}}updateTooltipClass(e){let t=this.tooltipClass(e);if(t!=this.currentClass){for(let n of this.currentClass.split(" "))n&&this.dom.classList.remove(n);for(let n of t.split(" "))n&&this.dom.classList.add(n);this.currentClass=t}}positioned(e){this.space=e,this.info&&this.view.requestMeasure(this.placeInfoReq)}updateSel(){let e=this.view.state.field(this.stateField),t=e.open;(t.selected>-1&&t.selected<this.range.from||t.selected>=this.range.to)&&(this.range=ep(t.options.length,t.selected,this.view.state.facet(Ae).maxRenderedOptions),this.showOptions(t.options,e.id));let n=this.updateSelectedOption(t.selected);if(n){this.destroyInfo();let{completion:r}=t.options[t.selected],{info:s}=r;if(!s)return;let o=typeof s=="string"?document.createTextNode(s):s(r);if(!o)return;"then"in o?o.then(l=>{l&&this.view.state.field(this.stateField,!1)==e&&this.addInfoPane(l,r)}).catch(l=>_e(this.view.state,l,"completion info")):(this.addInfoPane(o,r),n.setAttribute("aria-describedby",this.info.id))}}addInfoPane(e,t){this.destroyInfo();let n=this.info=document.createElement("div");if(n.className="cm-tooltip cm-completionInfo",n.id="cm-completionInfo-"+Math.floor(Math.random()*65535).toString(16),e.nodeType!=null)n.appendChild(e),this.infoDestroy=null;else{let{dom:r,destroy:s}=e;n.appendChild(r),this.infoDestroy=s||null}this.dom.appendChild(n),this.view.requestMeasure(this.placeInfoReq)}updateSelectedOption(e){let t=null;for(let n=this.list.firstChild,r=this.range.from;n;n=n.nextSibling,r++)n.nodeName!="LI"||!n.id?r--:r==e?n.hasAttribute("aria-selected")||(n.setAttribute("aria-selected","true"),t=n):n.hasAttribute("aria-selected")&&(n.removeAttribute("aria-selected"),n.removeAttribute("aria-describedby"));return t&&b2(this.list,t),t}measureInfo(){let e=this.dom.querySelector("[aria-selected]");if(!e||!this.info)return null;let t=this.dom.getBoundingClientRect(),n=this.info.getBoundingClientRect(),r=e.getBoundingClientRect(),s=this.space;if(!s){let o=this.dom.ownerDocument.documentElement;s={left:0,top:0,right:o.clientWidth,bottom:o.clientHeight}}return r.top>Math.min(s.bottom,t.bottom)-10||r.bottom<Math.max(s.top,t.top)+10?null:this.view.state.facet(Ae).positionInfo(this.view,t,r,n,s,this.dom)}placeInfo(e){this.info&&(e?(e.style&&(this.info.style.cssText=e.style),this.info.className="cm-tooltip cm-completionInfo "+(e.class||"")):this.info.style.cssText="top: -1e6px")}createListBox(e,t,n){let r=document.createElement("ul");r.id=t,r.setAttribute("role","listbox"),r.setAttribute("aria-expanded","true"),r.setAttribute("aria-label",this.view.state.phrase("Completions")),r.addEventListener("mousedown",o=>{o.target==r&&o.preventDefault()});let s=null;for(let o=n.from;o<n.to;o++){let{completion:l,match:a}=e[o],{section:c}=l;if(c){let u=typeof c=="string"?c:c.name;if(u!=s&&(o>n.from||n.from==0))if(s=u,typeof c!="string"&&c.header)r.appendChild(c.header(c));else{let d=r.appendChild(document.createElement("completion-section"));d.textContent=u}}let h=r.appendChild(document.createElement("li"));h.id=t+"-"+o,h.setAttribute("role","option");let f=this.optionClass(l);f&&(h.className=f);for(let u of this.optionContent){let d=u(l,this.view.state,this.view,a);d&&h.appendChild(d)}}return n.from&&r.classList.add("cm-completionListIncompleteTop"),n.to<e.length&&r.classList.add("cm-completionListIncompleteBottom"),r}destroyInfo(){this.info&&(this.infoDestroy&&this.infoDestroy(),this.info.remove(),this.info=null)}destroy(){this.destroyInfo()}};function m2(i,e){return t=>new sp(t,i,e)}function b2(i,e){let t=i.getBoundingClientRect(),n=e.getBoundingClientRect(),r=t.height/i.offsetHeight;n.top<t.top?i.scrollTop-=(t.top-n.top)/r:n.bottom>t.bottom&&(i.scrollTop+=(n.bottom-t.bottom)/r)}function Wy(i){return(i.boost||0)*100+(i.apply?10:0)+(i.info?5:0)+(i.type?1:0)}function y2(i,e){let t=[],n=null,r=null,s=h=>{t.push(h);let{section:f}=h.completion;if(f){n||(n=[]);let u=typeof f=="string"?f:f.name;n.some(d=>d.name==u)||n.push(typeof f=="string"?{name:u}:f)}},o=e.facet(Ae);for(let h of i)if(h.hasResult()){let f=h.result.getMatch;if(h.result.filter===!1)for(let u of h.result.options)s(new Uc(u,h.source,f?f(u):[],1e9-t.length));else{let u=e.sliceDoc(h.from,h.to),d,p=o.filterStrict?new rp(u):new np(u);for(let O of h.result.options)if(d=p.match(O.label)){let g=O.displayLabel?f?f(O,d.matched):[]:d.matched,b=d.score+(O.boost||0);if(s(new Uc(O,h.source,g,b)),typeof O.section=="object"&&O.section.rank==="dynamic"){let{name:w}=O.section;r||(r=Object.create(null)),r[w]=Math.max(b,r[w]||-1e9)}}}}if(n){let h=Object.create(null),f=0,u=(d,p)=>(d.rank==="dynamic"&&p.rank==="dynamic"?r[p.name]-r[d.name]:0)||(typeof d.rank=="number"?d.rank:1e9)-(typeof p.rank=="number"?p.rank:1e9)||(d.name<p.name?-1:1);for(let d of n.sort(u))f-=1e5,h[d.name]=f;for(let d of t){let{section:p}=d.completion;p&&(d.score+=h[typeof p=="string"?p:p.name])}}let l=[],a=null,c=o.compareCompletions;for(let h of t.sort((f,u)=>u.score-f.score||c(f.completion,u.completion))){let f=h.completion;!a||a.label!=f.label||a.detail!=f.detail||a.type!=null&&f.type!=null&&a.type!=f.type||a.apply!=f.apply||a.boost!=f.boost?l.push(h):Wy(h.completion)>Wy(a)&&(l[l.length-1]=h),a=h.completion}return l}var op=class i{constructor(e,t,n,r,s,o){this.options=e,this.attrs=t,this.tooltip=n,this.timestamp=r,this.selected=s,this.disabled=o}setSelected(e,t){return e==this.selected||e>=this.options.length?this:new i(this.options,Ny(t,e),this.tooltip,this.timestamp,e,this.disabled)}static build(e,t,n,r,s,o){if(r&&!o&&e.some(c=>c.isPending))return r.setDisabled();let l=y2(e,t);if(!l.length)return r&&e.some(c=>c.isPending)?r.setDisabled():null;let a=t.facet(Ae).selectOnOpen?0:-1;if(r&&r.selected!=a&&r.selected!=-1){let c=r.options[r.selected].completion;for(let h=0;h<l.length;h++)if(l[h].completion==c){a=h;break}}return new i(l,Ny(n,a),{pos:e.reduce((c,h)=>h.hasResult()?Math.min(c,h.from):c,1e8),create:v2,above:s.aboveCursor},r?r.timestamp:Date.now(),a,!1)}map(e){return new i(this.options,this.attrs,{...this.tooltip,pos:e.mapPos(this.tooltip.pos)},this.timestamp,this.selected,this.disabled)}setDisabled(){return new i(this.options,this.attrs,this.tooltip,this.timestamp,this.selected,!0)}},lp=class i{constructor(e,t,n){this.active=e,this.id=t,this.open=n}static start(){return new i(k2,"cm-ac-"+Math.floor(Math.random()*2e6).toString(36),null)}update(e){let{state:t}=e,n=t.facet(Ae),s=(n.override||t.languageDataAt("autocomplete",Kn(t)).map(p2)).map(a=>(this.active.find(h=>h.source==a)||new Pi(a,this.active.some(h=>h.state!=0)?1:0)).update(e,n));s.length==this.active.length&&s.every((a,c)=>a==this.active[c])&&(s=this.active);let o=this.open,l=e.effects.some(a=>a.is(Op));o&&e.docChanged&&(o=o.map(e.changes)),e.selection||s.some(a=>a.hasResult()&&e.changes.touchesRange(a.from,a.to))||!S2(s,this.active)||l?o=op.build(s,t,this.id,o,n,l):o&&o.disabled&&!s.some(a=>a.isPending)&&(o=null),!o&&s.every(a=>!a.isPending)&&s.some(a=>a.hasResult())&&(s=s.map(a=>a.hasResult()?new Pi(a.source,0):a));for(let a of e.effects)a.is(pp)&&(o=o&&o.setSelected(a.value,this.id));return s==this.active&&o==this.open?this:new i(s,this.id,o)}get tooltip(){return this.open?this.open.tooltip:null}get attrs(){return this.open?this.open.attrs:this.active.length?x2:w2}};function S2(i,e){if(i==e)return!0;for(let t=0,n=0;;){for(;t<i.length&&!i[t].hasResult();)t++;for(;n<e.length&&!e[n].hasResult();)n++;let r=t==i.length,s=n==e.length;if(r||s)return r==s;if(i[t++].result!=e[n++].result)return!1}}var x2={"aria-autocomplete":"list"},w2={};function Ny(i,e){let t={"aria-autocomplete":"list","aria-haspopup":"listbox","aria-controls":i};return e>-1&&(t["aria-activedescendant"]=i+"-"+e),t}var k2=[];function Jy(i,e){if(i.isUserEvent("input.complete")){let n=i.annotation(dp);if(n&&e.activateOnCompletion(n))return 12}let t=i.isUserEvent("input.type");return t&&e.activateOnTyping?5:t?1:i.isUserEvent("delete.backward")?2:i.selection?8:i.docChanged?16:0}var Pi=class i{constructor(e,t,n=!1){this.source=e,this.state=t,this.explicit=n}hasResult(){return!1}get isPending(){return this.state==1}update(e,t){let n=Jy(e,t),r=this;(n&8||n&16&&this.touches(e))&&(r=new i(r.source,0)),n&4&&r.state==0&&(r=new i(this.source,1)),r=r.updateFor(e,n);for(let s of e.effects)if(s.is(Bc))r=new i(r.source,1,s.value);else if(s.is(jo))r=new i(r.source,0);else if(s.is(Op))for(let o of s.value)o.source==r.source&&(r=o);return r}updateFor(e,t){return this.map(e.changes)}map(e){return this}touches(e){return e.changes.touchesRange(Kn(e.state))}},Vc=class i extends Pi{constructor(e,t,n,r,s,o){super(e,3,t),this.limit=n,this.result=r,this.from=s,this.to=o}hasResult(){return!0}updateFor(e,t){var n;if(!(t&3))return this.map(e.changes);let r=this.result;r.map&&!e.changes.empty&&(r=r.map(r,e.changes));let s=e.changes.mapPos(this.from),o=e.changes.mapPos(this.to,1),l=Kn(e.state);if(l>o||!r||t&2&&(Kn(e.startState)==this.from||l<this.limit))return new Pi(this.source,t&4?1:0);let a=e.changes.mapPos(this.limit);return Q2(r.validFor,e.state,s,o)?new i(this.source,this.explicit,a,r,s,o):r.update&&(r=r.update(r,s,o,new fs(e.state,l,!1)))?new i(this.source,this.explicit,a,r,r.from,(n=r.to)!==null&&n!==void 0?n:Kn(e.state)):new Pi(this.source,1,this.explicit)}map(e){if(e.empty)return this;let t=this.result.map?this.result.map(this.result,e):this.result;return t?new i(this.source,this.explicit,e.mapPos(this.limit),t,e.mapPos(this.from),e.mapPos(this.to,1)):new Pi(this.source,0)}touches(e){return e.changes.touchesRange(this.from,this.to)}};function Q2(i,e,t,n){if(!i)return!1;let r=e.sliceDoc(t,n);return typeof i=="function"?i(r,t,n,e):Ky(i,!0).test(r)}var Op=Y.define({map(i,e){return i.map(t=>t.map(e))}}),ut=de.define({create(){return lp.start()},update(i,e){return i.update(e)},provide:i=>[xo.from(i,e=>e.tooltip),M.contentAttributes.from(i,e=>e.attrs)]});function gp(i,e){let t=e.completion.apply||e.completion.label,n=i.state.field(ut).active.find(r=>r.source==e.source);return n instanceof Vc?(typeof t=="string"?i.dispatch({...d2(i.state,t,n.from,n.to),annotations:dp.of(e.completion)}):t(i,e.completion,n.from,n.to),!0):!1}var v2=m2(ut,gp);function Ic(i,e="option"){return t=>{let n=t.state.field(ut,!1);if(!n||!n.open||n.open.disabled||Date.now()-n.open.timestamp<t.state.facet(Ae).interactionDelay)return!1;let r=1,s;e=="page"&&(s=td(t,n.open.tooltip))&&(r=Math.max(2,Math.floor(s.dom.offsetHeight/s.dom.querySelector("li").offsetHeight)-1));let{length:o}=n.open.options,l=n.open.selected>-1?n.open.selected+r*(i?1:-1):i?0:o-1;return l<0?l=e=="page"?0:o-1:l>=o&&(l=e=="page"?o-1:0),t.dispatch({effects:pp.of(l)}),!0}}var $2=i=>{let e=i.state.field(ut,!1);return i.state.readOnly||!e||!e.open||e.open.selected<0||e.open.disabled||Date.now()-e.open.timestamp<i.state.facet(Ae).interactionDelay?!1:gp(i,e.open.options[e.open.selected])},tp=i=>i.state.field(ut,!1)?(i.dispatch({effects:Bc.of(!0)}),!0):!1,P2=i=>{let e=i.state.field(ut,!1);return!e||!e.active.some(t=>t.state!=0)?!1:(i.dispatch({effects:jo.of(null)}),!0)},ap=class{constructor(e,t){this.active=e,this.context=t,this.time=Date.now(),this.updates=[],this.done=void 0}},C2=50,T2=1e3,A2=se.fromClass(class{constructor(i){this.view=i,this.debounceUpdate=-1,this.running=[],this.debounceAccept=-1,this.pendingStart=!1,this.composing=0;for(let e of i.state.field(ut).active)e.isPending&&this.startQuery(e)}update(i){let e=i.state.field(ut),t=i.state.facet(Ae);if(!i.selectionSet&&!i.docChanged&&i.startState.field(ut)==e)return;let n=i.transactions.some(s=>{let o=Jy(s,t);return o&8||(s.selection||s.docChanged)&&!(o&3)});for(let s=0;s<this.running.length;s++){let o=this.running[s];if(n||o.context.abortOnDocChange&&i.docChanged||o.updates.length+i.transactions.length>C2&&Date.now()-o.time>T2){for(let l of o.context.abortListeners)try{l()}catch(a){_e(this.view.state,a)}o.context.abortListeners=null,this.running.splice(s--,1)}else o.updates.push(...i.transactions)}this.debounceUpdate>-1&&clearTimeout(this.debounceUpdate),i.transactions.some(s=>s.effects.some(o=>o.is(Bc)))&&(this.pendingStart=!0);let r=this.pendingStart?50:t.activateOnTypingDelay;if(this.debounceUpdate=e.active.some(s=>s.isPending&&!this.running.some(o=>o.active.source==s.source))?setTimeout(()=>this.startUpdate(),r):-1,this.composing!=0)for(let s of i.transactions)s.isUserEvent("input.type")?this.composing=2:this.composing==2&&s.selection&&(this.composing=3)}startUpdate(){this.debounceUpdate=-1,this.pendingStart=!1;let{state:i}=this.view,e=i.field(ut);for(let t of e.active)t.isPending&&!this.running.some(n=>n.active.source==t.source)&&this.startQuery(t);this.running.length&&e.open&&e.open.disabled&&(this.debounceAccept=setTimeout(()=>this.accept(),this.view.state.facet(Ae).updateSyncTime))}startQuery(i){let{state:e}=this.view,t=Kn(e),n=new fs(e,t,i.explicit,this.view),r=new ap(i,n);this.running.push(r),Promise.resolve(i.source(n)).then(s=>{r.context.aborted||(r.done=s||null,this.scheduleAccept())},s=>{this.view.dispatch({effects:jo.of(null)}),_e(this.view.state,s)})}scheduleAccept(){this.running.every(i=>i.done!==void 0)?this.accept():this.debounceAccept<0&&(this.debounceAccept=setTimeout(()=>this.accept(),this.view.state.facet(Ae).updateSyncTime))}accept(){var i;this.debounceAccept>-1&&clearTimeout(this.debounceAccept),this.debounceAccept=-1;let e=[],t=this.view.state.facet(Ae),n=this.view.state.field(ut);for(let r=0;r<this.running.length;r++){let s=this.running[r];if(s.done===void 0)continue;if(this.running.splice(r--,1),s.done){let l=Kn(s.updates.length?s.updates[0].startState:this.view.state),a=Math.min(l,s.done.from+(s.active.explicit?0:1)),c=new Vc(s.active.source,s.active.explicit,a,s.done,s.done.from,(i=s.done.to)!==null&&i!==void 0?i:l);for(let h of s.updates)c=c.update(h,t);if(c.hasResult()){e.push(c);continue}}let o=n.active.find(l=>l.source==s.active.source);if(o&&o.isPending)if(s.done==null){let l=new Pi(s.active.source,0);for(let a of s.updates)l=l.update(a,t);l.isPending||e.push(l)}else this.startQuery(o)}(e.length||n.open&&n.open.disabled)&&this.view.dispatch({effects:Op.of(e)})}},{eventHandlers:{blur(i){let e=this.view.state.field(ut,!1);if(e&&e.tooltip&&this.view.state.facet(Ae).closeOnBlur){let t=e.open&&td(this.view,e.open.tooltip);(!t||!t.dom.contains(i.relatedTarget))&&setTimeout(()=>this.view.dispatch({effects:jo.of(null)}),10)}},compositionstart(){this.composing=1},compositionend(){this.composing==3&&setTimeout(()=>this.view.dispatch({effects:Bc.of(!1)}),20),this.composing=0}}}),M2=typeof navigator=="object"&&/Win/.test(navigator.platform),R2=at.highest(M.domEventHandlers({keydown(i,e){let t=e.state.field(ut,!1);if(!t||!t.open||t.open.disabled||t.open.selected<0||i.key.length>1||i.ctrlKey&&!(M2&&i.altKey)||i.metaKey)return!1;let n=t.open.options[t.open.selected],r=t.active.find(o=>o.source==n.source),s=n.completion.commitCharacters||r.result.commitCharacters;return s&&s.indexOf(i.key)>-1&&gp(e,n),!1}})),eS=M.baseTheme({".cm-tooltip.cm-tooltip-autocomplete":{"& > ul":{fontFamily:"monospace",whiteSpace:"nowrap",overflow:"hidden auto",maxWidth_fallback:"700px",maxWidth:"min(700px, 95vw)",minWidth:"250px",maxHeight:"10em",height:"100%",listStyle:"none",margin:0,padding:0,"& > li, & > completion-section":{padding:"1px 3px",lineHeight:1.2},"& > li":{overflowX:"hidden",textOverflow:"ellipsis",cursor:"pointer"},"& > completion-section":{display:"list-item",borderBottom:"1px solid silver",paddingLeft:"0.5em",opacity:.7}}},"&light .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#17c",color:"white"},"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#777"},"&dark .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#347",color:"white"},"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#444"},".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after":{content:'"\xB7\xB7\xB7"',opacity:.5,display:"block",textAlign:"center",cursor:"pointer"},".cm-tooltip.cm-completionInfo":{position:"absolute",padding:"3px 9px",width:"max-content",maxWidth:"400px",boxSizing:"border-box",whiteSpace:"pre-line"},".cm-completionInfo.cm-completionInfo-left":{right:"100%"},".cm-completionInfo.cm-completionInfo-right":{left:"100%"},".cm-completionInfo.cm-completionInfo-left-narrow":{right:"30px"},".cm-completionInfo.cm-completionInfo-right-narrow":{left:"30px"},"&light .cm-snippetField":{backgroundColor:"#00000022"},"&dark .cm-snippetField":{backgroundColor:"#ffffff22"},".cm-snippetFieldPosition":{verticalAlign:"text-top",width:0,height:"1.15em",display:"inline-block",margin:"0 -0.7px -.7em",borderLeft:"1.4px dotted #888"},".cm-completionMatchedText":{textDecoration:"underline"},".cm-completionDetail":{marginLeft:"0.5em",fontStyle:"italic"},".cm-completionIcon":{fontSize:"90%",width:".8em",display:"inline-block",textAlign:"center",paddingRight:".6em",opacity:"0.6",boxSizing:"content-box"},".cm-completionIcon-function, .cm-completionIcon-method":{"&:after":{content:"'\u0192'"}},".cm-completionIcon-class":{"&:after":{content:"'\u25CB'"}},".cm-completionIcon-interface":{"&:after":{content:"'\u25CC'"}},".cm-completionIcon-variable":{"&:after":{content:"'\u{1D465}'"}},".cm-completionIcon-constant":{"&:after":{content:"'\u{1D436}'"}},".cm-completionIcon-type":{"&:after":{content:"'\u{1D461}'"}},".cm-completionIcon-enum":{"&:after":{content:"'\u222A'"}},".cm-completionIcon-property":{"&:after":{content:"'\u25A1'"}},".cm-completionIcon-keyword":{"&:after":{content:"'\u{1F511}\uFE0E'"}},".cm-completionIcon-namespace":{"&:after":{content:"'\u25A2'"}},".cm-completionIcon-text":{"&:after":{content:"'abc'",fontSize:"50%",verticalAlign:"middle"}}}),cp=class{constructor(e,t,n,r){this.field=e,this.line=t,this.from=n,this.to=r}},hp=class i{constructor(e,t,n){this.field=e,this.from=t,this.to=n}map(e){let t=e.mapPos(this.from,-1,$e.TrackDel),n=e.mapPos(this.to,1,$e.TrackDel);return t==null||n==null?null:new i(this.field,t,n)}},fp=class i{constructor(e,t){this.lines=e,this.fieldPositions=t}instantiate(e,t){let n=[],r=[t],s=e.doc.lineAt(t),o=/^\s*/.exec(s.text)[0];for(let a of this.lines){if(n.length){let c=o,h=/^\t*/.exec(a)[0].length;for(let f=0;f<h;f++)c+=e.facet(Ji);r.push(t+c.length-h),a=c+a.slice(h)}n.push(a),t+=a.length+1}let l=this.fieldPositions.map(a=>new hp(a.field,r[a.line]+a.from,r[a.line]+a.to));return{text:n,ranges:l}}static parse(e){let t=[],n=[],r=[],s;for(let o of e.split(/\r\n?|\n/)){for(;s=/[#$]\{(?:(\d+)(?::([^{}]*))?|((?:\\[{}]|[^{}])*))\}/.exec(o);){let l=s[1]?+s[1]:null,a=s[2]||s[3]||"",c=-1;l===0&&(l=1e9);let h=a.replace(/\\[{}]/g,f=>f[1]);for(let f=0;f<t.length;f++)(l!=null?t[f].seq==l:h&&t[f].name==h)&&(c=f);if(c<0){let f=0;for(;f<t.length&&(l==null||t[f].seq!=null&&t[f].seq<l);)f++;t.splice(f,0,{seq:l,name:h}),c=f;for(let u of r)u.field>=c&&u.field++}for(let f of r)if(f.line==n.length&&f.from>s.index){let u=s[2]?3+(s[1]||"").length:2;f.from-=u,f.to-=u}r.push(new cp(c,n.length,s.index,s.index+h.length)),o=o.slice(0,s.index)+a+o.slice(s.index+s[0].length)}o=o.replace(/\\([{}])/g,(l,a,c)=>{for(let h of r)h.line==n.length&&h.from>c&&(h.from--,h.to--);return a}),n.push(o)}return new i(n,r)}},X2=L.widget({widget:new class extends De{toDOM(){let i=document.createElement("span");return i.className="cm-snippetFieldPosition",i}ignoreEvent(){return!1}}}),E2=L.mark({class:"cm-snippetField"}),us=class i{constructor(e,t){this.ranges=e,this.active=t,this.deco=L.set(e.map(n=>(n.from==n.to?X2:E2).range(n.from,n.to)),!0)}map(e){let t=[];for(let n of this.ranges){let r=n.map(e);if(!r)return null;t.push(r)}return new i(t,this.active)}selectionInsideField(e){return e.ranges.every(t=>this.ranges.some(n=>n.field==this.active&&n.from<=t.from&&n.to>=t.to))}},Bo=Y.define({map(i,e){return i&&i.map(e)}}),Z2=Y.define(),Io=de.define({create(){return null},update(i,e){for(let t of e.effects){if(t.is(Bo))return t.value;if(t.is(Z2)&&i)return new us(i.ranges,t.value)}return i&&e.docChanged&&(i=i.map(e.changes)),i&&e.selection&&!i.selectionInsideField(e.selection)&&(i=null),i},provide:i=>M.decorations.from(i,e=>e?e.deco:L.none)});function mp(i,e){return v.create(i.filter(t=>t.field==e).map(t=>v.range(t.from,t.to)))}function _2(i){let e=fp.parse(i);return(t,n,r,s)=>{let{text:o,ranges:l}=e.instantiate(t.state,r),{main:a}=t.state.selection,c={changes:{from:r,to:s==a.from?a.to:s,insert:q.of(o)},scrollIntoView:!0,annotations:n?[dp.of(n),xe.userEvent.of("input.complete")]:void 0};if(l.length&&(c.selection=mp(l,0)),l.some(h=>h.field>0)){let h=new us(l,0),f=c.effects=[Bo.of(h)];t.state.field(Io,!1)===void 0&&f.push(Y.appendConfig.of([Io,U2,B2,eS]))}t.dispatch(t.state.update(c))}}function tS(i){return({state:e,dispatch:t})=>{let n=e.field(Io,!1);if(!n||i<0&&n.active==0)return!1;let r=n.active+i,s=i>0&&!n.ranges.some(o=>o.field==r+i);return t(e.update({selection:mp(n.ranges,r),effects:Bo.of(s?null:new us(n.ranges,r)),scrollIntoView:!0})),!0}}var L2=({state:i,dispatch:e})=>i.field(Io,!1)?(e(i.update({effects:Bo.of(null)})),!0):!1,D2=tS(1),j2=tS(-1);var I2=[{key:"Tab",run:D2,shift:j2},{key:"Escape",run:L2}],qy=R.define({combine(i){return i.length?i[0]:I2}}),U2=at.highest(Qi.compute([qy],i=>i.facet(qy)));function it(i,e){return{...e,apply:_2(i)}}var B2=M.domEventHandlers({mousedown(i,e){let t=e.state.field(Io,!1),n;if(!t||(n=e.posAtCoords({x:i.clientX,y:i.clientY}))==null)return!1;let r=t.ranges.find(s=>s.from<=n&&s.to>=n);return!r||r.field==t.active?!1:(e.dispatch({selection:mp(t.ranges,r.field),effects:Bo.of(t.ranges.some(s=>s.field>r.field)?new us(t.ranges,r.field):null),scrollIntoView:!0}),!0)}});var Uo={brackets:["(","[","{","'",'"'],before:")]}:;>",stringPrefixes:[]},Hn=Y.define({map(i,e){let t=e.mapPos(i,-1,$e.TrackAfter);return t??void 0}}),bp=new class extends Ct{};bp.startSide=1;bp.endSide=-1;var iS=de.define({create(){return G.empty},update(i,e){if(i=i.map(e.changes),e.selection){let t=e.state.doc.lineAt(e.selection.main.head);i=i.update({filter:n=>n>=t.from&&n<=t.to})}for(let t of e.effects)t.is(Hn)&&(i=i.update({add:[bp.range(t.value,t.value+1)]}));return i}});function nS(){return[Y2,iS]}var ip="()[]{}<>\xAB\xBB\xBB\xAB\uFF3B\uFF3D\uFF5B\uFF5D";function rS(i){for(let e=0;e<ip.length;e+=2)if(ip.charCodeAt(e)==i)return ip.charAt(e+1);return io(i<128?i:i+1)}function sS(i,e){return i.languageDataAt("closeBrackets",e)[0]||Uo}var V2=typeof navigator=="object"&&/Android\b/.test(navigator.userAgent),Y2=M.inputHandler.of((i,e,t,n)=>{if((V2?i.composing:i.compositionStarted)||i.state.readOnly)return!1;let r=i.state.selection.main;if(n.length>2||n.length==2&&mt(Ze(n,0))==1||e!=r.from||t!=r.to)return!1;let s=W2(i.state,n);return s?(i.dispatch(s),!0):!1}),z2=({state:i,dispatch:e})=>{if(i.readOnly)return!1;let n=sS(i,i.selection.main.head).brackets||Uo.brackets,r=null,s=i.changeByRange(o=>{if(o.empty){let l=N2(i.doc,o.head);for(let a of n)if(a==l&&Yc(i.doc,o.head)==rS(Ze(a,0)))return{changes:{from:o.head-a.length,to:o.head+a.length},range:v.cursor(o.head-a.length)}}return{range:r=o}});return r||e(i.update(s,{scrollIntoView:!0,userEvent:"delete.backward"})),!r},oS=[{key:"Backspace",run:z2}];function W2(i,e){let t=sS(i,i.selection.main.head),n=t.brackets||Uo.brackets;for(let r of n){let s=rS(Ze(r,0));if(e==r)return s==r?F2(i,r,n.indexOf(r+r+r)>-1,t):q2(i,r,s,t.before||Uo.before);if(e==s&&lS(i,i.selection.main.from))return G2(i,r,s)}return null}function lS(i,e){let t=!1;return i.field(iS).between(0,i.doc.length,n=>{n==e&&(t=!0)}),t}function Yc(i,e){let t=i.sliceString(e,e+2);return t.slice(0,mt(Ze(t,0)))}function N2(i,e){let t=i.sliceString(e-2,e);return mt(Ze(t,0))==t.length?t:t.slice(1)}function q2(i,e,t,n){let r=null,s=i.changeByRange(o=>{if(!o.empty)return{changes:[{insert:e,from:o.from},{insert:t,from:o.to}],effects:Hn.of(o.to+e.length),range:v.range(o.anchor+e.length,o.head+e.length)};let l=Yc(i.doc,o.head);return!l||/\s/.test(l)||n.indexOf(l)>-1?{changes:{insert:e+t,from:o.head},effects:Hn.of(o.head+e.length),range:v.cursor(o.head+e.length)}:{range:r=o}});return r?null:i.update(s,{scrollIntoView:!0,userEvent:"input.type"})}function G2(i,e,t){let n=null,r=i.changeByRange(s=>s.empty&&Yc(i.doc,s.head)==t?{changes:{from:s.head,to:s.head+t.length,insert:t},range:v.cursor(s.head+t.length)}:n={range:s});return n?null:i.update(r,{scrollIntoView:!0,userEvent:"input.type"})}function F2(i,e,t,n){let r=n.stringPrefixes||Uo.stringPrefixes,s=null,o=i.changeByRange(l=>{if(!l.empty)return{changes:[{insert:e,from:l.from},{insert:e,from:l.to}],effects:Hn.of(l.to+e.length),range:v.range(l.anchor+e.length,l.head+e.length)};let a=l.head,c=Yc(i.doc,a),h;if(c==e){if(Gy(i,a))return{changes:{insert:e+e,from:a},effects:Hn.of(a+e.length),range:v.cursor(a+e.length)};if(lS(i,a)){let u=t&&i.sliceDoc(a,a+e.length*3)==e+e+e?e+e+e:e;return{changes:{from:a,to:a+u.length,insert:u},range:v.cursor(a+u.length)}}}else{if(t&&i.sliceDoc(a-2*e.length,a)==e+e&&(h=Fy(i,a-2*e.length,r))>-1&&Gy(i,h))return{changes:{insert:e+e+e+e,from:a},effects:Hn.of(a+e.length),range:v.cursor(a+e.length)};if(i.charCategorizer(a)(c)!=ae.Word&&Fy(i,a,r)>-1&&!H2(i,a,e,r))return{changes:{insert:e+e,from:a},effects:Hn.of(a+e.length),range:v.cursor(a+e.length)}}return{range:s=l}});return s?null:i.update(o,{scrollIntoView:!0,userEvent:"input.type"})}function Gy(i,e){let t=ee(i).resolveInner(e+1);return t.parent&&t.from==e}function H2(i,e,t,n){let r=ee(i).resolveInner(e,-1),s=n.reduce((o,l)=>Math.max(o,l.length),0);for(let o=0;o<5;o++){let l=i.sliceDoc(r.from,Math.min(r.to,r.from+t.length+s)),a=l.indexOf(t);if(!a||a>-1&&n.indexOf(l.slice(0,a))>-1){let h=r.firstChild;for(;h&&h.from==r.from&&h.to-h.from>t.length+a;){if(i.sliceDoc(h.to-t.length,h.to)==t)return!1;h=h.firstChild}return!0}let c=r.to==e&&r.parent;if(!c)break;r=c}return!1}function Fy(i,e,t){let n=i.charCategorizer(e);if(n(i.sliceDoc(e-1,e))!=ae.Word)return e;for(let r of t){let s=e-r.length;if(i.sliceDoc(s,e)==r&&n(i.sliceDoc(s-1,s))!=ae.Word)return s}return-1}function aS(i={}){return[R2,ut,Ae.of(i),A2,K2,eS]}var yp=[{key:"Ctrl-Space",run:tp},{mac:"Alt-`",run:tp},{mac:"Alt-i",run:tp},{key:"Escape",run:P2},{key:"ArrowDown",run:Ic(!0)},{key:"ArrowUp",run:Ic(!1)},{key:"PageDown",run:Ic(!0,"page")},{key:"PageUp",run:Ic(!1,"page")},{key:"Enter",run:$2}],K2=at.highest(Qi.computeN([Ae],i=>i.facet(Ae).defaultKeymap?[yp]:[]));var Wc=class{constructor(e,t,n){this.from=e,this.to=t,this.diagnostic=n}},Jn=class i{constructor(e,t,n){this.diagnostics=e,this.panel=t,this.selected=n}static init(e,t,n){let r=n.facet(Vo).markerFilter;r&&(e=r(e,n));let s=e.slice().sort((d,p)=>d.from-p.from||d.to-p.to),o=new lt,l=[],a=0,c=n.doc.iter(),h=0,f=n.doc.length;for(let d=0;;){let p=d==s.length?null:s[d];if(!p&&!l.length)break;let O,g;if(l.length)O=a,g=l.reduce((x,X)=>Math.min(x,X.to),p&&p.from>O?p.from:1e8);else{if(O=p.from,O>f)break;g=p.to,l.push(p),d++}for(;d<s.length;){let x=s[d];if(x.from==O&&(x.to>x.from||x.to==O))l.push(x),d++,g=Math.min(x.to,g);else{g=Math.min(x.from,g);break}}g=Math.min(g,f);let b=!1;if(l.some(x=>x.from==O&&(x.to==g||g==f))&&(b=O==g,!b&&g-O<10)){let x=O-(h+c.value.length);x>0&&(c.next(x),h=O);for(let X=O;;){if(X>=g){b=!0;break}if(!c.lineBreak&&h+c.value.length>X)break;X=h+c.value.length,h+=c.value.length,c.next()}}let w=cA(l);if(b)o.add(O,O,L.widget({widget:new Sp(w),diagnostics:l.slice()}));else{let x=l.reduce((X,C)=>C.markClass?X+" "+C.markClass:X,"");o.add(O,g,L.mark({class:"cm-lintRange cm-lintRange-"+w+x,diagnostics:l.slice(),inclusiveEnd:l.some(X=>X.to>g)}))}if(a=g,a==f)break;for(let x=0;x<l.length;x++)l[x].to<=a&&l.splice(x--,1)}let u=o.finish();return new i(u,t,rn(u))}};function rn(i,e=null,t=0){let n=null;return i.between(t,1e9,(r,s,{spec:o})=>{if(!(e&&o.diagnostics.indexOf(e)<0))if(!n)n=new Wc(r,s,e||o.diagnostics[0]);else{if(o.diagnostics.indexOf(n.diagnostic)<0)return!1;n=new Wc(n.from,s,n.diagnostic)}}),n}function J2(i,e){let t=e.pos,n=e.end||t,r=i.state.facet(Vo).hideOn(i,t,n);if(r!=null)return r;let s=i.startState.doc.lineAt(e.pos);return!!(i.effects.some(o=>o.is(fS))||i.changes.touchesRange(s.from,Math.max(s.to,n)))}function eA(i,e){return i.field(wt,!1)?e:e.concat(Y.appendConfig.of(hA))}var fS=Y.define(),xp=Y.define(),uS=Y.define(),wt=de.define({create(){return new Jn(L.none,null,null)},update(i,e){if(e.docChanged&&i.diagnostics.size){let t=i.diagnostics.map(e.changes),n=null,r=i.panel;if(i.selected){let s=e.changes.mapPos(i.selected.from,1);n=rn(t,i.selected.diagnostic,s)||rn(t,null,s)}!t.size&&r&&e.state.facet(Vo).autoPanel&&(r=null),i=new Jn(t,r,n)}for(let t of e.effects)if(t.is(fS)){let n=e.state.facet(Vo).autoPanel?t.value.length?Yo.open:null:i.panel;i=Jn.init(t.value,n,e.state)}else t.is(xp)?i=new Jn(i.diagnostics,t.value?Yo.open:null,i.selected):t.is(uS)&&(i=new Jn(i.diagnostics,i.panel,t.value));return i},provide:i=>[Vn.from(i,e=>e.panel),M.decorations.from(i,e=>e.diagnostics)]});var tA=L.mark({class:"cm-lintRange cm-lintRange-active"});function iA(i,e,t){let{diagnostics:n}=i.state.field(wt),r,s=-1,o=-1;n.between(e-(t<0?1:0),e+(t>0?1:0),(a,c,{spec:h})=>{if(e>=a&&e<=c&&(a==c||(e>a||t>0)&&(e<c||t<0)))return r=h.diagnostics,s=a,o=c,!1});let l=i.state.facet(Vo).tooltipFilter;return r&&l&&(r=l(r,i.state)),r?{pos:s,end:o,above:!0,create(){return{dom:nA(i,r)}}}:null}function nA(i,e){return te("ul",{class:"cm-tooltip-lint"},e.map(t=>OS(i,t,!1)))}var rA=i=>{let e=i.state.field(wt,!1);(!e||!e.panel)&&i.dispatch({effects:eA(i.state,[xp.of(!0)])});let t=is(i,Yo.open);return t&&t.dom.querySelector(".cm-panel-lint ul").focus(),!0},cS=i=>{let e=i.state.field(wt,!1);return!e||!e.panel?!1:(i.dispatch({effects:xp.of(!1)}),!0)},sA=i=>{let e=i.state.field(wt,!1);if(!e)return!1;let t=i.state.selection.main,n=rn(e.diagnostics,null,t.to+1);return!n&&(n=rn(e.diagnostics,null,0),!n||n.from==t.from&&n.to==t.to)?!1:(i.dispatch({selection:{anchor:n.from,head:n.to},scrollIntoView:!0}),B1(i,n.from,1,{tooltip:gS,until:r=>r.docChanged||r.newSelection.main.head<n.from||r.newSelection.main.head>n.to}),!0)};var dS=[{key:"Mod-Shift-m",run:rA,preventDefault:!0},{key:"F8",run:sA}];var Vo=R.define({combine(i){return{sources:i.map(e=>e.source).filter(e=>e!=null),...Ye(i.map(e=>e.config),{delay:750,markerFilter:null,tooltipFilter:null,needsRefresh:null,hideOn:()=>null},{delay:Math.max,markerFilter:hS,tooltipFilter:hS,needsRefresh:(e,t)=>e?t?n=>e(n)||t(n):e:t,hideOn:(e,t)=>e?t?(n,r,s)=>e(n,r,s)||t(n,r,s):e:t,autoPanel:(e,t)=>e||t})}}});function hS(i,e){return i?e?(t,n)=>e(i(t,n),n):i:e}function pS(i){let e=[];if(i)e:for(let{name:t}of i){for(let n=0;n<t.length;n++){let r=t[n];if(/[a-zA-Z]/.test(r)&&!e.some(s=>s.toLowerCase()==r.toLowerCase())){e.push(r);continue e}}e.push("")}return e}function OS(i,e,t){var n;let r=t?pS(e.actions):[];return te("li",{class:"cm-diagnostic cm-diagnostic-"+e.severity},te("span",{class:"cm-diagnosticText"},e.renderMessage?e.renderMessage(i):e.message),(n=e.actions)===null||n===void 0?void 0:n.map((s,o)=>{let l=!1,a=d=>{if(d.preventDefault(),l)return;l=!0;let p=rn(i.state.field(wt).diagnostics,e);p&&s.apply(i,p.from,p.to)},{name:c}=s,h=r[o]?c.indexOf(r[o]):-1,f=h<0?c:[c.slice(0,h),te("u",c.slice(h,h+1)),c.slice(h+1)],u=s.markClass?" "+s.markClass:"";return te("button",{type:"button",class:"cm-diagnosticAction"+u,onclick:a,onmousedown:a,"aria-label":` Action: ${c}${h<0?"":` (access key "${r[o]})"`}.`},f)}),e.source&&te("div",{class:"cm-diagnosticSource"},e.source))}var Sp=class extends De{constructor(e){super(),this.sev=e}eq(e){return e.sev==this.sev}toDOM(){return te("span",{class:"cm-lintPoint cm-lintPoint-"+this.sev})}},Nc=class{constructor(e,t){this.diagnostic=t,this.id="item_"+Math.floor(Math.random()*4294967295).toString(16),this.dom=OS(e,t,!0),this.dom.id=this.id,this.dom.setAttribute("role","option")}},Yo=class i{constructor(e){this.view=e,this.items=[];let t=r=>{if(!(r.ctrlKey||r.altKey||r.metaKey)){if(r.keyCode==27)cS(this.view),this.view.focus();else if(r.keyCode==38||r.keyCode==33)this.moveSelection((this.selectedIndex-1+this.items.length)%this.items.length);else if(r.keyCode==40||r.keyCode==34)this.moveSelection((this.selectedIndex+1)%this.items.length);else if(r.keyCode==36)this.moveSelection(0);else if(r.keyCode==35)this.moveSelection(this.items.length-1);else if(r.keyCode==13)this.view.focus();else if(r.keyCode>=65&&r.keyCode<=90&&this.selectedIndex>=0){let{diagnostic:s}=this.items[this.selectedIndex],o=pS(s.actions);for(let l=0;l<o.length;l++)if(o[l].toUpperCase().charCodeAt(0)==r.keyCode){let a=rn(this.view.state.field(wt).diagnostics,s);a&&s.actions[l].apply(e,a.from,a.to)}}else return;r.preventDefault()}},n=r=>{for(let s=0;s<this.items.length;s++)this.items[s].dom.contains(r.target)&&this.moveSelection(s)};this.list=te("ul",{tabIndex:0,role:"listbox","aria-label":this.view.state.phrase("Diagnostics"),onkeydown:t,onclick:n}),this.dom=te("div",{class:"cm-panel-lint"},this.list,te("button",{type:"button",name:"close","aria-label":this.view.state.phrase("close"),onclick:()=>cS(this.view)},"\xD7")),this.update()}get selectedIndex(){let e=this.view.state.field(wt).selected;if(!e)return-1;for(let t=0;t<this.items.length;t++)if(this.items[t].diagnostic==e.diagnostic)return t;return-1}update(){let{diagnostics:e,selected:t}=this.view.state.field(wt),n=0,r=!1,s=null,o=new Set;for(e.between(0,this.view.state.doc.length,(l,a,{spec:c})=>{for(let h of c.diagnostics){if(o.has(h))continue;o.add(h);let f=-1,u;for(let d=n;d<this.items.length;d++)if(this.items[d].diagnostic==h){f=d;break}f<0?(u=new Nc(this.view,h),this.items.splice(n,0,u),r=!0):(u=this.items[f],f>n&&(this.items.splice(n,f-n),r=!0)),t&&u.diagnostic==t.diagnostic?u.dom.hasAttribute("aria-selected")||(u.dom.setAttribute("aria-selected","true"),s=u):u.dom.hasAttribute("aria-selected")&&u.dom.removeAttribute("aria-selected"),n++}});n<this.items.length&&!(this.items.length==1&&this.items[0].diagnostic.from<0);)r=!0,this.items.pop();this.items.length==0&&(this.items.push(new Nc(this.view,{from:-1,to:-1,severity:"info",message:this.view.state.phrase("No diagnostics")})),r=!0),s?(this.list.setAttribute("aria-activedescendant",s.id),this.view.requestMeasure({key:this,read:()=>({sel:s.dom.getBoundingClientRect(),panel:this.list.getBoundingClientRect()}),write:({sel:l,panel:a})=>{let c=a.height/this.list.offsetHeight;l.top<a.top?this.list.scrollTop-=(a.top-l.top)/c:l.bottom>a.bottom&&(this.list.scrollTop+=(l.bottom-a.bottom)/c)}})):this.selectedIndex<0&&this.list.removeAttribute("aria-activedescendant"),r&&this.sync()}sync(){let e=this.list.firstChild;function t(){let n=e;e=n.nextSibling,n.remove()}for(let n of this.items)if(n.dom.parentNode==this.list){for(;e!=n.dom;)t();e=n.dom.nextSibling}else this.list.insertBefore(n.dom,e);for(;e;)t()}moveSelection(e){if(this.selectedIndex<0)return;let t=this.view.state.field(wt),n=rn(t.diagnostics,this.items[e].diagnostic);n&&this.view.dispatch({selection:{anchor:n.from,head:n.to},scrollIntoView:!0,effects:uS.of(n)})}static open(e){return new i(e)}};function oA(i,e='viewBox="0 0 40 40"'){return`url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${e}>${encodeURIComponent(i)}</svg>')`}function zc(i){return oA(`<path d="m0 2.5 l2 -1.5 l1 0 l2 1.5 l1 0" stroke="${i}" fill="none" stroke-width=".7"/>`,'width="6" height="3"')}var lA=M.baseTheme({".cm-diagnostic":{padding:"3px 6px 3px 8px",marginLeft:"-1px",display:"block",whiteSpace:"pre-wrap"},".cm-diagnostic-error":{borderLeft:"5px solid #d11"},".cm-diagnostic-warning":{borderLeft:"5px solid orange"},".cm-diagnostic-info":{borderLeft:"5px solid #999"},".cm-diagnostic-hint":{borderLeft:"5px solid #66d"},".cm-diagnosticAction":{font:"inherit",border:"none",padding:"2px 4px",backgroundColor:"#444",color:"white",borderRadius:"3px",marginLeft:"8px",cursor:"pointer"},".cm-diagnosticSource":{fontSize:"70%",opacity:.7},".cm-lintRange":{backgroundPosition:"left bottom",backgroundRepeat:"repeat-x",paddingBottom:"0.7px"},".cm-lintRange-error":{backgroundImage:zc("#f11")},".cm-lintRange-warning":{backgroundImage:zc("orange")},".cm-lintRange-info":{backgroundImage:zc("#999")},".cm-lintRange-hint":{backgroundImage:zc("#66d")},".cm-lintRange-active":{backgroundColor:"#ffdd9980"},".cm-tooltip-lint":{padding:0,margin:0},".cm-lintPoint":{position:"relative","&:after":{content:'""',position:"absolute",bottom:0,left:"-2px",borderLeft:"3px solid transparent",borderRight:"3px solid transparent",borderBottom:"4px solid #d11"}},".cm-lintPoint-warning":{"&:after":{borderBottomColor:"orange"}},".cm-lintPoint-info":{"&:after":{borderBottomColor:"#999"}},".cm-lintPoint-hint":{"&:after":{borderBottomColor:"#66d"}},".cm-panel.cm-panel-lint":{position:"relative","& ul":{maxHeight:"100px",overflowY:"auto","& [aria-selected]":{backgroundColor:"#ddd","& u":{textDecoration:"underline"}},"&:focus [aria-selected]":{background_fallback:"#bdf",backgroundColor:"Highlight",color_fallback:"white",color:"HighlightText"},"& u":{textDecoration:"none"},padding:0,margin:0},"& [name=close]":{position:"absolute",top:"0",right:"2px",background:"inherit",border:"none",font:"inherit",padding:0,margin:0}},"&dark .cm-lintRange-active":{backgroundColor:"#86714a80"},"&dark .cm-panel.cm-panel-lint ul":{"& [aria-selected]":{backgroundColor:"#2e343e"}}});function aA(i){return i=="error"?4:i=="warning"?3:i=="info"?2:1}function cA(i){let e="hint",t=1;for(let n of i){let r=aA(n.severity);r>t&&(t=r,e=n.severity)}return e}var gS=U1(iA,{hideOn:J2}),hA=[wt,M.decorations.compute([wt],i=>{let{selected:e,panel:t}=i.field(wt);return!e||!t||e.from==e.to?L.none:L.set([tA.range(e.from,e.to)])}),gS,lA];var fA=[G1(),F1(),_1(),ty(),_b(),R1(),Z1(),ne.allowMultipleSelections.of(!0),Cb(),Db(jb,{fallback:!0}),Vb(),nS(),aS(),D1(),j1(),L1(),Ly(),Qi.of([...oS,...My,...By,...sy,...Xb,...yp,...dS])];var Fc=class i{static create(e,t,n,r,s){let o=r+(r<<8)+e+(t<<4)|0;return new i(e,t,n,o,s,[],[])}constructor(e,t,n,r,s,o,l){this.type=e,this.value=t,this.from=n,this.hash=r,this.end=s,this.children=o,this.positions=l,this.hashProp=[[U.contextHash,r]]}addChild(e,t){e.prop(U.contextHash)!=this.hash&&(e=new J(e.type,e.children,e.positions,e.length,this.hashProp)),this.children.push(e),this.positions.push(t)}toTree(e,t=this.end){let n=this.children.length-1;return n>=0&&(t=Math.max(t,this.positions[n]+this.children[n].length+this.from)),new J(e.types[this.type],this.children,this.positions,t-this.from).balance({makeTree:(r,s,o)=>new J(Oe.none,r,s,o,this.hashProp)})}},A;(function(i){i[i.Document=1]="Document",i[i.CodeBlock=2]="CodeBlock",i[i.FencedCode=3]="FencedCode",i[i.Blockquote=4]="Blockquote",i[i.HorizontalRule=5]="HorizontalRule",i[i.BulletList=6]="BulletList",i[i.OrderedList=7]="OrderedList",i[i.ListItem=8]="ListItem",i[i.ATXHeading1=9]="ATXHeading1",i[i.ATXHeading2=10]="ATXHeading2",i[i.ATXHeading3=11]="ATXHeading3",i[i.ATXHeading4=12]="ATXHeading4",i[i.ATXHeading5=13]="ATXHeading5",i[i.ATXHeading6=14]="ATXHeading6",i[i.SetextHeading1=15]="SetextHeading1",i[i.SetextHeading2=16]="SetextHeading2",i[i.HTMLBlock=17]="HTMLBlock",i[i.LinkReference=18]="LinkReference",i[i.Paragraph=19]="Paragraph",i[i.CommentBlock=20]="CommentBlock",i[i.ProcessingInstructionBlock=21]="ProcessingInstructionBlock",i[i.Escape=22]="Escape",i[i.Entity=23]="Entity",i[i.HardBreak=24]="HardBreak",i[i.Emphasis=25]="Emphasis",i[i.StrongEmphasis=26]="StrongEmphasis",i[i.Link=27]="Link",i[i.Image=28]="Image",i[i.InlineCode=29]="InlineCode",i[i.HTMLTag=30]="HTMLTag",i[i.Comment=31]="Comment",i[i.ProcessingInstruction=32]="ProcessingInstruction",i[i.Autolink=33]="Autolink",i[i.HeaderMark=34]="HeaderMark",i[i.QuoteMark=35]="QuoteMark",i[i.ListMark=36]="ListMark",i[i.LinkMark=37]="LinkMark",i[i.EmphasisMark=38]="EmphasisMark",i[i.CodeMark=39]="CodeMark",i[i.CodeText=40]="CodeText",i[i.CodeInfo=41]="CodeInfo",i[i.LinkTitle=42]="LinkTitle",i[i.LinkLabel=43]="LinkLabel",i[i.URL=44]="URL"})(A||(A={}));var Qp=class{constructor(e,t){this.start=e,this.content=t,this.marks=[],this.parsers=[]}},vp=class{constructor(){this.text="",this.baseIndent=0,this.basePos=0,this.depth=0,this.markers=[],this.pos=0,this.indent=0,this.next=-1}forward(){this.basePos>this.pos&&this.forwardInner()}forwardInner(){let e=this.skipSpace(this.basePos);this.indent=this.countIndent(e,this.pos,this.indent),this.pos=e,this.next=e==this.text.length?-1:this.text.charCodeAt(e)}skipSpace(e){return Wo(this.text,e)}reset(e){for(this.text=e,this.baseIndent=this.basePos=this.pos=this.indent=0,this.forwardInner(),this.depth=1;this.markers.length;)this.markers.pop()}moveBase(e){this.basePos=e,this.baseIndent=this.countIndent(e,this.pos,this.indent)}moveBaseColumn(e){this.baseIndent=e,this.basePos=this.findColumn(e)}addMarker(e){this.markers.push(e)}countIndent(e,t=0,n=0){for(let r=t;r<e;r++)n+=this.text.charCodeAt(r)==9?4-n%4:1;return n}findColumn(e){let t=0;for(let n=0;t<this.text.length&&n<e;t++)n+=this.text.charCodeAt(t)==9?4-n%4:1;return t}scrub(){if(!this.baseIndent)return this.text;let e="";for(let t=0;t<this.basePos;t++)e+=" ";return e+this.text.slice(this.basePos)}};function mS(i,e,t){if(t.pos==t.text.length||i!=e.block&&t.indent>=e.stack[t.depth+1].value+t.baseIndent)return!0;if(t.indent>=t.baseIndent+4)return!1;let n=(i.type==A.OrderedList?Lp:_p)(t,e,!1);return n>0&&(i.type!=A.BulletList||Zp(t,e,!1)<0)&&t.text.charCodeAt(t.pos+n-1)==i.value}var CS={[A.Blockquote](i,e,t){return t.next!=62?!1:(t.markers.push(K(A.QuoteMark,e.lineStart+t.pos,e.lineStart+t.pos+1)),t.moveBase(t.pos+(Zt(t.text.charCodeAt(t.pos+1))?2:1)),i.end=e.lineStart+t.text.length,!0)},[A.ListItem](i,e,t){return t.indent<t.baseIndent+i.value&&t.next>-1?!1:(t.moveBaseColumn(t.baseIndent+i.value),!0)},[A.OrderedList]:mS,[A.BulletList]:mS,[A.Document](){return!0}};function Zt(i){return i==32||i==9||i==10||i==13}function Wo(i,e=0){for(;e<i.length&&Zt(i.charCodeAt(e));)e++;return e}function bS(i,e,t){for(;e>t&&Zt(i.charCodeAt(e-1));)e--;return e}function TS(i){if(i.next!=96&&i.next!=126)return-1;let e=i.pos+1;for(;e<i.text.length&&i.text.charCodeAt(e)==i.next;)e++;if(e<i.pos+3)return-1;if(i.next==96){for(let t=e;t<i.text.length;t++)if(i.text.charCodeAt(t)==96)return-1}return e}function AS(i){return i.next!=62?-1:i.text.charCodeAt(i.pos+1)==32?2:1}function Zp(i,e,t){if(i.next!=42&&i.next!=45&&i.next!=95)return-1;let n=1;for(let r=i.pos+1;r<i.text.length;r++){let s=i.text.charCodeAt(r);if(s==i.next)n++;else if(!Zt(s))return-1}return t&&i.next==45&&XS(i)>-1&&i.depth==e.stack.length&&e.parser.leafBlockParsers.indexOf(LS.SetextHeading)>-1||n<3?-1:1}function MS(i,e){for(let t=i.stack.length-1;t>=0;t--)if(i.stack[t].type==e)return!0;return!1}function _p(i,e,t){return(i.next==45||i.next==43||i.next==42)&&(i.pos==i.text.length-1||Zt(i.text.charCodeAt(i.pos+1)))&&(!t||MS(e,A.BulletList)||i.skipSpace(i.pos+2)<i.text.length)?1:-1}function Lp(i,e,t){let n=i.pos,r=i.next;for(;r>=48&&r<=57;){n++;if(n==i.text.length)return-1;r=i.text.charCodeAt(n)}return n==i.pos||n>i.pos+9||r!=46&&r!=41||n<i.text.length-1&&!Zt(i.text.charCodeAt(n+1))||t&&!MS(e,A.OrderedList)&&(i.skipSpace(n+1)==i.text.length||n>i.pos+1||i.next!=49)?-1:n+1-i.pos}function RS(i){if(i.next!=35)return-1;let e=i.pos+1;for(;e<i.text.length&&i.text.charCodeAt(e)==35;)e++;if(e<i.text.length&&i.text.charCodeAt(e)!=32)return-1;let t=e-i.pos;return t>6?-1:t}function XS(i){if(i.next!=45&&i.next!=61||i.indent>=i.baseIndent+4)return-1;let e=i.pos+1;for(;e<i.text.length&&i.text.charCodeAt(e)==i.next;)e++;let t=e;for(;e<i.text.length&&Zt(i.text.charCodeAt(e));)e++;return e==i.text.length?t:-1}var $p=/^[ \t]*$/,ES=/-->/,ZS=/\?>/,Pp=[[/^<(?:script|pre|style)(?:\s|>|$)/i,/<\/(?:script|pre|style)>/i],[/^\s*<!--/,ES],[/^\s*<\?/,ZS],[/^\s*<![A-Z]/,/>/],[/^\s*<!\[CDATA\[/,/\]\]>/],[/^\s*<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|\/?>|$)/i,$p],[/^\s*(?:<\/[a-z][\w-]*\s*>|<[a-z][\w-]*(\s+[a-z:_][\w-.]*(?:\s*=\s*(?:[^\s"'=<>`]+|'[^']*'|"[^"]*"))?)*\s*>)\s*$/i,$p]];function _S(i,e,t){if(i.next!=60)return-1;let n=i.text.slice(i.pos);for(let r=0,s=Pp.length-(t?1:0);r<s;r++)if(Pp[r][0].test(n))return r;return-1}function yS(i,e){let t=i.countIndent(e,i.pos,i.indent),n=i.skipSpace(e),r=i.countIndent(n,e,t);return r>=t+5||n==i.text.length?t+1:r}function sn(i,e,t){let n=i.length-1;n>=0&&i[n].to==e&&i[n].type==A.CodeText?i[n].to=t:i.push(K(A.CodeText,e,t))}var qc={LinkReference:void 0,IndentedCode(i,e){let t=e.baseIndent+4;if(e.indent<t)return!1;let n=e.findColumn(t),r=i.lineStart+n,s=i.lineStart+e.text.length,o=[],l=[];for(sn(o,r,s);i.nextLine()&&e.depth>=i.stack.length;)if(e.pos==e.text.length){sn(l,i.lineStart-1,i.lineStart);for(let a of e.markers)l.push(a)}else{if(e.indent<t)break;{if(l.length){for(let c of l)c.type==A.CodeText?sn(o,c.from,c.to):o.push(c);l=[]}sn(o,i.lineStart-1,i.lineStart);for(let c of e.markers)o.push(c);s=i.lineStart+e.text.length;let a=i.lineStart+e.findColumn(e.baseIndent+4);a<s&&sn(o,a,s)}}return l.length&&(l=l.filter(a=>a.type!=A.CodeText),l.length&&(e.markers=l.concat(e.markers))),i.addNode(i.buffer.writeElements(o,-r).finish(A.CodeBlock,s-r),r),!0},FencedCode(i,e){let t=TS(e);if(t<0)return!1;let n=i.lineStart+e.pos,r=e.next,s=t-e.pos,o=e.skipSpace(t),l=bS(e.text,e.text.length,o),a=[K(A.CodeMark,n,n+s)];o<l&&a.push(K(A.CodeInfo,i.lineStart+o,i.lineStart+l));for(let c=!0,h=!0,f=!1;i.nextLine()&&e.depth>=i.stack.length;c=!1){let u=e.pos;if(e.indent-e.baseIndent<4)for(;u<e.text.length&&e.text.charCodeAt(u)==r;)u++;if(u-e.pos>=s&&e.skipSpace(u)==e.text.length){for(let d of e.markers)a.push(d);h&&f&&sn(a,i.lineStart-1,i.lineStart),a.push(K(A.CodeMark,i.lineStart+e.pos,i.lineStart+u)),i.nextLine();break}else{f=!0,c||(sn(a,i.lineStart-1,i.lineStart),h=!1);for(let O of e.markers)a.push(O);let d=i.lineStart+e.basePos,p=i.lineStart+e.text.length;d<p&&(sn(a,d,p),h=!1)}}return i.addNode(i.buffer.writeElements(a,-n).finish(A.FencedCode,i.prevLineEnd()-n),n),!0},Blockquote(i,e){let t=AS(e);return t<0?!1:(i.startContext(A.Blockquote,e.pos),i.addNode(A.QuoteMark,i.lineStart+e.pos,i.lineStart+e.pos+1),e.moveBase(e.pos+t),null)},HorizontalRule(i,e){if(Zp(e,i,!1)<0)return!1;let t=i.lineStart+e.pos;return i.nextLine(),i.addNode(A.HorizontalRule,t),!0},BulletList(i,e){let t=_p(e,i,!1);if(t<0)return!1;i.block.type!=A.BulletList&&i.startContext(A.BulletList,e.basePos,e.next);let n=yS(e,e.pos+1);return i.startContext(A.ListItem,e.basePos,n-e.baseIndent),i.addNode(A.ListMark,i.lineStart+e.pos,i.lineStart+e.pos+t),e.moveBaseColumn(n),null},OrderedList(i,e){let t=Lp(e,i,!1);if(t<0)return!1;i.block.type!=A.OrderedList&&i.startContext(A.OrderedList,e.basePos,e.text.charCodeAt(e.pos+t-1));let n=yS(e,e.pos+t);return i.startContext(A.ListItem,e.basePos,n-e.baseIndent),i.addNode(A.ListMark,i.lineStart+e.pos,i.lineStart+e.pos+t),e.moveBaseColumn(n),null},ATXHeading(i,e){let t=RS(e);if(t<0)return!1;let n=e.pos,r=i.lineStart+n,s=bS(e.text,e.text.length,n),o=s;for(;o>n&&e.text.charCodeAt(o-1)==e.next;)o--;(o==s||o==n||!Zt(e.text.charCodeAt(o-1)))&&(o=e.text.length);let l=i.buffer.write(A.HeaderMark,0,t).writeElements(i.parser.parseInline(e.text.slice(n+t+1,o),r+t+1),-r);o<e.text.length&&l.write(A.HeaderMark,o-n,s-n);let a=l.finish(A.ATXHeading1-1+t,e.text.length-n);return i.nextLine(),i.addNode(a,r),!0},HTMLBlock(i,e){let t=_S(e,i,!1);if(t<0)return!1;let n=i.lineStart+e.pos,r=Pp[t][1],s=[],o=r!=$p;for(;!r.test(e.text)&&i.nextLine();){if(e.depth<i.stack.length){o=!1;break}for(let c of e.markers)s.push(c)}o&&i.nextLine();let l=r==ES?A.CommentBlock:r==ZS?A.ProcessingInstructionBlock:A.HTMLBlock,a=i.prevLineEnd();return i.addNode(i.buffer.writeElements(s,-n).finish(l,a-n),n),!0},SetextHeading:void 0},Cp=class{constructor(e){this.stage=0,this.elts=[],this.pos=0,this.start=e.start,this.advance(e.content)}nextLine(e,t,n){if(this.stage==-1)return!1;let r=n.content+` +`+t.scrub(),s=this.advance(r);return s>-1&&s<r.length?this.complete(e,n,s):!1}finish(e,t){return(this.stage==2||this.stage==3)&&Wo(t.content,this.pos)==t.content.length?this.complete(e,t,t.content.length):!1}complete(e,t,n){return e.addLeafElement(t,K(A.LinkReference,this.start,this.start+n,this.elts)),!0}nextStage(e){return e?(this.pos=e.to-this.start,this.elts.push(e),this.stage++,!0):(e===!1&&(this.stage=-1),!1)}advance(e){for(;;){if(this.stage==-1)return-1;if(this.stage==0){if(!this.nextStage(YS(e,this.pos,this.start,!0)))return-1;if(e.charCodeAt(this.pos)!=58)return this.stage=-1;this.elts.push(K(A.LinkMark,this.pos+this.start,this.pos+this.start+1)),this.pos++}else if(this.stage==1){if(!this.nextStage(BS(e,Wo(e,this.pos),this.start)))return-1}else if(this.stage==2){let t=Wo(e,this.pos),n=0;if(t>this.pos){let r=VS(e,t,this.start);if(r){let s=wp(e,r.to-this.start);s>0&&(this.nextStage(r),n=s)}}return n||(n=wp(e,this.pos)),n>0&&n<e.length?n:-1}else return wp(e,this.pos)}}};function wp(i,e){for(;e<i.length;e++){let t=i.charCodeAt(e);if(t==10)break;if(!Zt(t))return-1}return e}var Tp=class{nextLine(e,t,n){let r=t.depth<e.stack.length?-1:XS(t),s=t.next;if(r<0)return!1;let o=K(A.HeaderMark,e.lineStart+t.pos,e.lineStart+r);return e.nextLine(),e.addLeafElement(n,K(s==61?A.SetextHeading1:A.SetextHeading2,n.start,e.prevLineEnd(),[...e.parser.parseInline(n.content,n.start),o])),!0}finish(){return!1}},LS={LinkReference(i,e){return e.content.charCodeAt(0)==91?new Cp(e):null},SetextHeading(){return new Tp}},uA=[(i,e)=>RS(e)>=0,(i,e)=>TS(e)>=0,(i,e)=>AS(e)>=0,(i,e)=>_p(e,i,!0)>=0,(i,e)=>Lp(e,i,!0)>=0,(i,e)=>Zp(e,i,!0)>=0,(i,e)=>_S(e,i,!0)>=0],dA={text:"",end:0},Ap=class{constructor(e,t,n,r){this.parser=e,this.input=t,this.ranges=r,this.line=new vp,this.atEnd=!1,this.reusePlaceholders=new Map,this.stoppedAt=null,this.rangeI=0,this.to=r[r.length-1].to,this.lineStart=this.absoluteLineStart=this.absoluteLineEnd=r[0].from,this.block=Fc.create(A.Document,0,this.lineStart,0,0),this.stack=[this.block],this.fragments=n.length?new Xp(n,t):null,this.readLine()}get parsedPos(){return this.absoluteLineStart}advance(){if(this.stoppedAt!=null&&this.absoluteLineStart>this.stoppedAt)return this.finish();let{line:e}=this;for(;;){for(let n=0;;){let r=e.depth<this.stack.length?this.stack[this.stack.length-1]:null;for(;n<e.markers.length&&(!r||e.markers[n].from<r.end);){let s=e.markers[n++];this.addNode(s.type,s.from,s.to)}if(!r)break;this.finishContext()}if(e.pos<e.text.length)break;if(!this.nextLine())return this.finish()}if(this.fragments&&this.reuseFragment(e.basePos))return null;e:for(;;){for(let n of this.parser.blockParsers)if(n){let r=n(this,e);if(r!=!1){if(r==!0)return null;e.forward();continue e}}break}if(e.pos==e.text.length)return this.nextLine()?null:this.finish();let t=new Qp(this.lineStart+e.pos,e.text.slice(e.pos));for(let n of this.parser.leafBlockParsers)if(n){let r=n(this,t);r&&t.parsers.push(r)}e:for(;this.nextLine()&&e.pos!=e.text.length;){if(e.indent<e.baseIndent+4){for(let n of this.parser.endLeafBlock)if(n(this,e,t))break e}for(let n of t.parsers)if(n.nextLine(this,e,t))return null;t.content+=` +`+e.scrub();for(let n of e.markers)t.marks.push(n)}return this.finishLeaf(t),null}stopAt(e){if(this.stoppedAt!=null&&this.stoppedAt<e)throw new RangeError("Can't move stoppedAt forward");this.stoppedAt=e}reuseFragment(e){if(!this.fragments.moveTo(this.absoluteLineStart+e,this.absoluteLineStart)||!this.fragments.matches(this.block.hash))return!1;let t=this.fragments.takeNodes(this);return t?(this.absoluteLineStart+=t,this.lineStart=zS(this.absoluteLineStart,this.ranges),this.moveRangeI(),this.absoluteLineStart<this.to?(this.lineStart++,this.absoluteLineStart++,this.readLine()):(this.atEnd=!0,this.readLine()),!0):!1}get depth(){return this.stack.length}parentType(e=this.depth-1){return this.parser.nodeSet.types[this.stack[e].type]}nextLine(){return this.lineStart+=this.line.text.length,this.absoluteLineEnd>=this.to?(this.absoluteLineStart=this.absoluteLineEnd,this.atEnd=!0,this.readLine(),!1):(this.lineStart++,this.absoluteLineStart=this.absoluteLineEnd+1,this.moveRangeI(),this.readLine(),!0)}peekLine(){return this.scanLine(this.absoluteLineEnd+1).text}moveRangeI(){for(;this.rangeI<this.ranges.length-1&&this.absoluteLineStart>=this.ranges[this.rangeI].to;)this.rangeI++,this.absoluteLineStart=Math.max(this.absoluteLineStart,this.ranges[this.rangeI].from)}scanLine(e){let t=dA;if(t.end=e,e>=this.to)t.text="";else if(t.text=this.lineChunkAt(e),t.end+=t.text.length,this.ranges.length>1){let n=this.absoluteLineStart,r=this.rangeI;for(;this.ranges[r].to<t.end;){r++;let s=this.ranges[r].from,o=this.lineChunkAt(s);t.end=s+o.length,t.text=t.text.slice(0,this.ranges[r-1].to-n)+o,n=t.end-t.text.length}}return t}readLine(){let{line:e}=this,{text:t,end:n}=this.scanLine(this.absoluteLineStart);for(this.absoluteLineEnd=n,e.reset(t);e.depth<this.stack.length;e.depth++){let r=this.stack[e.depth],s=this.parser.skipContextMarkup[r.type];if(!s)throw new Error("Unhandled block context "+A[r.type]);let o=this.line.markers.length;if(!s(r,this,e)){this.line.markers.length>o&&(r.end=this.line.markers[this.line.markers.length-1].to),e.forward();break}e.forward()}}lineChunkAt(e){let t=this.input.chunk(e),n;if(this.input.lineChunks)n=t==` +`?"":t;else{let r=t.indexOf(` +`);n=r<0?t:t.slice(0,r)}return e+n.length>this.to?n.slice(0,this.to-e):n}prevLineEnd(){return this.atEnd?this.lineStart:this.lineStart-1}startContext(e,t,n=0){this.block=Fc.create(e,n,this.lineStart+t,this.block.hash,this.lineStart+this.line.text.length),this.stack.push(this.block)}startComposite(e,t,n=0){this.startContext(this.parser.getNodeType(e),t,n)}addNode(e,t,n){typeof e=="number"&&(e=new J(this.parser.nodeSet.types[e],ds,ds,(n??this.prevLineEnd())-t)),this.block.addChild(e,t-this.block.from)}addElement(e){this.block.addChild(e.toTree(this.parser.nodeSet),e.from-this.block.from)}addLeafElement(e,t){this.addNode(this.buffer.writeElements(Rp(t.children,e.marks),-t.from).finish(t.type,t.to-t.from),t.from)}finishContext(){let e=this.stack.pop(),t=this.stack[this.stack.length-1];t.addChild(e.toTree(this.parser.nodeSet),e.from-t.from),this.block=t}finish(){for(;this.stack.length>1;)this.finishContext();return this.addGaps(this.block.toTree(this.parser.nodeSet,this.lineStart))}addGaps(e){return this.ranges.length>1?DS(this.ranges,0,e.topNode,this.ranges[0].from,this.reusePlaceholders):e}finishLeaf(e){for(let n of e.parsers)if(n.finish(this,e))return;let t=Rp(this.parser.parseInline(e.content,e.start),e.marks);this.addNode(this.buffer.writeElements(t,-e.start).finish(A.Paragraph,e.content.length),e.start)}elt(e,t,n,r){return typeof e=="string"?K(this.parser.getNodeType(e),t,n,r):new Kc(e,t)}get buffer(){return new Hc(this.parser.nodeSet)}};function DS(i,e,t,n,r){let s=i[e].to,o=[],l=[],a=t.from+n;function c(h,f){for(;f?h>=s:h>s;){let u=i[e+1].from-s;n+=u,h+=u,e++,s=i[e].to}}for(let h=t.firstChild;h;h=h.nextSibling){c(h.from+n,!0);let f=h.from+n,u,d=r.get(h.tree);d?u=d:h.to+n>s?(u=DS(i,e,h,n,r),c(h.to+n,!1)):u=h.toTree(),o.push(u),l.push(f-a)}return c(t.to+n,!1),new J(t.type,o,l,t.to+n-a,t.tree?t.tree.propValues:void 0)}var qo=class i extends Ni{constructor(e,t,n,r,s,o,l,a,c){super(),this.nodeSet=e,this.blockParsers=t,this.leafBlockParsers=n,this.blockNames=r,this.endLeafBlock=s,this.skipContextMarkup=o,this.inlineParsers=l,this.inlineNames=a,this.wrappers=c,this.nodeTypes=Object.create(null);for(let h of e.types)this.nodeTypes[h.name]=h.id}createParse(e,t,n){let r=new Ap(this,e,t,n);for(let s of this.wrappers)r=s(r,e,t,n);return r}configure(e){let t=Mp(e);if(!t)return this;let{nodeSet:n,skipContextMarkup:r}=this,s=this.blockParsers.slice(),o=this.leafBlockParsers.slice(),l=this.blockNames.slice(),a=this.inlineParsers.slice(),c=this.inlineNames.slice(),h=this.endLeafBlock.slice(),f=this.wrappers;if(zo(t.defineNodes)){r=Object.assign({},r);let u=n.types.slice(),d;for(let p of t.defineNodes){let{name:O,block:g,composite:b,style:w}=typeof p=="string"?{name:p}:p;if(u.some(C=>C.name==O))continue;b&&(r[u.length]=(C,T,y)=>b(T,y,C.value));let x=u.length,X=b?["Block","BlockContext"]:g?x>=A.ATXHeading1&&x<=A.SetextHeading2?["Block","LeafBlock","Heading"]:["Block","LeafBlock"]:void 0;u.push(Oe.define({id:x,name:O,props:X&&[[U.group,X]]})),w&&(d||(d={}),Array.isArray(w)||w instanceof St?d[O]=w:Object.assign(d,w))}n=new zi(u),d&&(n=n.extend(Bt(d)))}if(zo(t.props)&&(n=n.extend(...t.props)),zo(t.remove))for(let u of t.remove){let d=this.blockNames.indexOf(u),p=this.inlineNames.indexOf(u);d>-1&&(s[d]=o[d]=void 0),p>-1&&(a[p]=void 0)}if(zo(t.parseBlock))for(let u of t.parseBlock){let d=l.indexOf(u.name);if(d>-1)s[d]=u.parse,o[d]=u.leaf;else{let p=u.before?Gc(l,u.before):u.after?Gc(l,u.after)+1:l.length-1;s.splice(p,0,u.parse),o.splice(p,0,u.leaf),l.splice(p,0,u.name)}u.endLeaf&&h.push(u.endLeaf)}if(zo(t.parseInline))for(let u of t.parseInline){let d=c.indexOf(u.name);if(d>-1)a[d]=u.parse;else{let p=u.before?Gc(c,u.before):u.after?Gc(c,u.after)+1:c.length-1;a.splice(p,0,u.parse),c.splice(p,0,u.name)}}return t.wrap&&(f=f.concat(t.wrap)),new i(n,s,o,l,h,r,a,c,f)}getNodeType(e){let t=this.nodeTypes[e];if(t==null)throw new RangeError(`Unknown node type '${e}'`);return t}parseInline(e,t){let n=new Fo(this,e,t);e:for(let r=t;r<n.end;){let s=n.char(r);for(let o of this.inlineParsers)if(o){let l=o(n,s,r);if(l>=0){r=l;continue e}}r++}return n.resolveMarkers(0)}};function zo(i){return i!=null&&i.length>0}function Mp(i){if(!Array.isArray(i))return i;if(i.length==0)return null;let e=Mp(i[0]);if(i.length==1)return e;let t=Mp(i.slice(1));if(!t||!e)return e||t;let n=(o,l)=>(o||ds).concat(l||ds),r=e.wrap,s=t.wrap;return{props:n(e.props,t.props),defineNodes:n(e.defineNodes,t.defineNodes),parseBlock:n(e.parseBlock,t.parseBlock),parseInline:n(e.parseInline,t.parseInline),remove:n(e.remove,t.remove),wrap:r?s?(o,l,a,c)=>r(s(o,l,a,c),l,a,c):r:s}}function Gc(i,e){let t=i.indexOf(e);if(t<0)throw new RangeError(`Position specified relative to unknown parser ${e}`);return t}var jS=[Oe.none];for(let i=1,e;e=A[i];i++)jS[i]=Oe.define({id:i,name:e,props:i>=A.Escape?[]:[[U.group,i in CS?["Block","BlockContext"]:["Block","LeafBlock"]]],top:e=="Document"});var ds=[],Hc=class{constructor(e){this.nodeSet=e,this.content=[],this.nodes=[]}write(e,t,n,r=0){return this.content.push(e,t,n,4+r*4),this}writeElements(e,t=0){for(let n of e)n.writeTo(this,t);return this}finish(e,t){return J.build({buffer:this.content,nodeSet:this.nodeSet,reused:this.nodes,topID:e,length:t})}},tr=class{constructor(e,t,n,r=ds){this.type=e,this.from=t,this.to=n,this.children=r}writeTo(e,t){let n=e.content.length;e.writeElements(this.children,t),e.content.push(this.type,this.from+t,this.to+t,e.content.length+4-n)}toTree(e){return new Hc(e).writeElements(this.children,-this.from).finish(this.type,this.to-this.from)}},Kc=class{constructor(e,t){this.tree=e,this.from=t}get to(){return this.from+this.tree.length}get type(){return this.tree.type.id}get children(){return ds}writeTo(e,t){e.nodes.push(this.tree),e.content.push(e.nodes.length-1,this.from+t,this.to+t,-1)}toTree(){return this.tree}};function K(i,e,t,n){return new tr(i,e,t,n)}var IS={resolve:"Emphasis",mark:"EmphasisMark"},US={resolve:"Emphasis",mark:"EmphasisMark"},er={},Jc={},nt=class{constructor(e,t,n,r){this.type=e,this.from=t,this.to=n,this.side=r}},SS="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",Go=/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\u2010-\u2027]/;try{Go=new RegExp("[\\p{S}|\\p{P}]","u")}catch{}var kp={Escape(i,e,t){if(e!=92||t==i.end-1)return-1;let n=i.char(t+1);for(let r=0;r<SS.length;r++)if(SS.charCodeAt(r)==n)return i.append(K(A.Escape,t,t+2));return-1},Entity(i,e,t){if(e!=38)return-1;let n=/^(?:#\d+|#x[a-f\d]+|\w+);/i.exec(i.slice(t+1,t+31));return n?i.append(K(A.Entity,t,t+1+n[0].length)):-1},InlineCode(i,e,t){if(e!=96||t&&i.char(t-1)==96)return-1;let n=t+1;for(;n<i.end&&i.char(n)==96;)n++;let r=n-t,s=0;for(;n<i.end;n++)if(i.char(n)==96){if(s++,s==r&&i.char(n+1)!=96)return i.append(K(A.InlineCode,t,n+1,[K(A.CodeMark,t,t+r),K(A.CodeMark,n+1-r,n+1)]))}else s=0;return-1},HTMLTag(i,e,t){if(e!=60||t==i.end-1)return-1;let n=i.slice(t+1,i.end),r=/^(?:[a-z][-\w+.]+:[^\s>]+|[a-z\d.!#$%&'*+/=?^_`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*)>/i.exec(n);if(r)return i.append(K(A.Autolink,t,t+1+r[0].length,[K(A.LinkMark,t,t+1),K(A.URL,t+1,t+r[0].length),K(A.LinkMark,t+r[0].length,t+1+r[0].length)]));let s=/^!--[^>](?:-[^-]|[^-])*?-->/i.exec(n);if(s)return i.append(K(A.Comment,t,t+1+s[0].length));let o=/^\?[^]*?\?>/.exec(n);if(o)return i.append(K(A.ProcessingInstruction,t,t+1+o[0].length));let l=/^(?:![A-Z][^]*?>|!\[CDATA\[[^]*?\]\]>|\/\s*[a-zA-Z][\w-]*\s*>|\s*[a-zA-Z][\w-]*(\s+[a-zA-Z:_][\w-.:]*(?:\s*=\s*(?:[^\s"'=<>`]+|'[^']*'|"[^"]*"))?)*\s*(\/\s*)?>)/.exec(n);return l?i.append(K(A.HTMLTag,t,t+1+l[0].length)):-1},Emphasis(i,e,t){if(e!=95&&e!=42)return-1;let n=t+1;for(;i.char(n)==e;)n++;let r=i.slice(t-1,t),s=i.slice(n,n+1),o=Go.test(r),l=Go.test(s),a=/\s|^$/.test(r),c=/\s|^$/.test(s),h=!c&&(!l||a||o),f=!a&&(!o||c||l),u=h&&(e==42||!f||o),d=f&&(e==42||!h||l);return i.append(new nt(e==95?IS:US,t,n,(u?1:0)|(d?2:0)))},HardBreak(i,e,t){if(e==92&&i.char(t+1)==10)return i.append(K(A.HardBreak,t,t+2));if(e==32){let n=t+1;for(;i.char(n)==32;)n++;if(i.char(n)==10&&n>=t+2)return i.append(K(A.HardBreak,t,n+1))}return-1},Link(i,e,t){return e==91?i.append(new nt(er,t,t+1,1)):-1},Image(i,e,t){return e==33&&i.char(t+1)==91?i.append(new nt(Jc,t,t+2,1)):-1},LinkEnd(i,e,t){if(e!=93)return-1;for(let n=i.parts.length-1;n>=0;n--){let r=i.parts[n];if(r instanceof nt&&(r.type==er||r.type==Jc)){if(!r.side||i.skipSpace(r.to)==t&&!/[(\[]/.test(i.slice(t+1,t+2)))return i.parts[n]=null,-1;let s=i.takeContent(n),o=i.parts[n]=pA(i,s,r.type==er?A.Link:A.Image,r.from,t+1);if(r.type==er)for(let l=0;l<n;l++){let a=i.parts[l];a instanceof nt&&a.type==er&&(a.side=0)}return o.to}}return-1}};function pA(i,e,t,n,r){let{text:s}=i,o=i.char(r),l=r;if(e.unshift(K(A.LinkMark,n,n+(t==A.Image?2:1))),e.push(K(A.LinkMark,r-1,r)),o==40){let a=i.skipSpace(r+1),c=BS(s,a-i.offset,i.offset),h;c&&(a=i.skipSpace(c.to),a!=c.to&&(h=VS(s,a-i.offset,i.offset),h&&(a=i.skipSpace(h.to)))),i.char(a)==41&&(e.push(K(A.LinkMark,r,r+1)),l=a+1,c&&e.push(c),h&&e.push(h),e.push(K(A.LinkMark,a,l)))}else if(o==91){let a=YS(s,r-i.offset,i.offset,!1);a&&(e.push(a),l=a.to)}return K(t,n,l,e)}function BS(i,e,t){if(i.charCodeAt(e)==60){for(let r=e+1;r<i.length;r++){let s=i.charCodeAt(r);if(s==62)return K(A.URL,e+t,r+1+t);if(s==60||s==10)return!1}return null}else{let r=0,s=e;for(let o=!1;s<i.length;s++){let l=i.charCodeAt(s);if(Zt(l))break;if(o)o=!1;else if(l==40)r++;else if(l==41){if(!r)break;r--}else l==92&&(o=!0)}return s>e?K(A.URL,e+t,s+t):s==i.length?null:!1}}function VS(i,e,t){let n=i.charCodeAt(e);if(n!=39&&n!=34&&n!=40)return!1;let r=n==40?41:n;for(let s=e+1,o=!1;s<i.length;s++){let l=i.charCodeAt(s);if(o)o=!1;else{if(l==r)return K(A.LinkTitle,e+t,s+1+t);l==92&&(o=!0)}}return null}function YS(i,e,t,n){for(let r=!1,s=e+1,o=Math.min(i.length,s+999);s<o;s++){let l=i.charCodeAt(s);if(r)r=!1;else{if(l==93)return n?!1:K(A.LinkLabel,e+t,s+1+t);if(n&&!Zt(l)&&(n=!1),l==91)return!1;l==92&&(r=!0)}}return null}var Fo=class{constructor(e,t,n){this.parser=e,this.text=t,this.offset=n,this.parts=[]}char(e){return e>=this.end?-1:this.text.charCodeAt(e-this.offset)}get end(){return this.offset+this.text.length}slice(e,t){return this.text.slice(e-this.offset,t-this.offset)}append(e){return this.parts.push(e),e.to}addDelimiter(e,t,n,r,s){return this.append(new nt(e,t,n,(r?1:0)|(s?2:0)))}get hasOpenLink(){for(let e=this.parts.length-1;e>=0;e--){let t=this.parts[e];if(t instanceof nt&&(t.type==er||t.type==Jc))return!0}return!1}addElement(e){return this.append(e)}resolveMarkers(e){for(let n=e;n<this.parts.length;n++){let r=this.parts[n];if(!(r instanceof nt&&r.type.resolve&&r.side&2))continue;let s=r.type==IS||r.type==US,o=r.to-r.from,l,a=n-1;for(;a>=e;a--){let O=this.parts[a];if(O instanceof nt&&O.side&1&&O.type==r.type&&!(s&&(r.side&1||O.side&2)&&(O.to-O.from+o)%3==0&&((O.to-O.from)%3||o%3))){l=O;break}}if(!l)continue;let c=r.type.resolve,h=[],f=l.from,u=r.to;if(s){let O=Math.min(2,l.to-l.from,o);f=l.to-O,u=r.from+O,c=O==1?"Emphasis":"StrongEmphasis"}l.type.mark&&h.push(this.elt(l.type.mark,f,l.to));for(let O=a+1;O<n;O++)this.parts[O]instanceof tr&&h.push(this.parts[O]),this.parts[O]=null;r.type.mark&&h.push(this.elt(r.type.mark,r.from,u));let d=this.elt(c,f,u,h);this.parts[a]=s&&l.from!=f?new nt(l.type,l.from,f,l.side):null,(this.parts[n]=s&&r.to!=u?new nt(r.type,u,r.to,r.side):null)?this.parts.splice(n,0,d):this.parts[n]=d}let t=[];for(let n=e;n<this.parts.length;n++){let r=this.parts[n];r instanceof tr&&t.push(r)}return t}findOpeningDelimiter(e){for(let t=this.parts.length-1;t>=0;t--){let n=this.parts[t];if(n instanceof nt&&n.type==e&&n.side&1)return t}return null}takeContent(e){let t=this.resolveMarkers(e);return this.parts.length=e,t}getDelimiterAt(e){let t=this.parts[e];return t instanceof nt?t:null}skipSpace(e){return Wo(this.text,e-this.offset)+this.offset}elt(e,t,n,r){return typeof e=="string"?K(this.parser.getNodeType(e),t,n,r):new Kc(e,t)}};Fo.linkStart=er;Fo.imageStart=Jc;function Rp(i,e){if(!e.length)return i;if(!i.length)return e;let t=i.slice(),n=0;for(let r of e){for(;n<t.length&&t[n].to<r.to;)n++;if(n<t.length&&t[n].from<r.from){let s=t[n];s instanceof tr&&(t[n]=new tr(s.type,s.from,s.to,Rp(s.children,[r])))}else t.splice(n++,0,r)}return t}var OA=[A.CodeBlock,A.ListItem,A.OrderedList,A.BulletList],Xp=class{constructor(e,t){this.fragments=e,this.input=t,this.i=0,this.fragment=null,this.fragmentEnd=-1,this.cursor=null,e.length&&(this.fragment=e[this.i++])}nextFragment(){this.fragment=this.i<this.fragments.length?this.fragments[this.i++]:null,this.cursor=null,this.fragmentEnd=-1}moveTo(e,t){for(;this.fragment&&this.fragment.to<=e;)this.nextFragment();if(!this.fragment||this.fragment.from>(e?e-1:0))return!1;if(this.fragmentEnd<0){let s=this.fragment.to;for(;s>0&&this.input.read(s-1,s)!=` +`;)s--;this.fragmentEnd=s?s-1:0}let n=this.cursor;n||(n=this.cursor=this.fragment.tree.cursor(),n.firstChild());let r=e+this.fragment.offset;for(;n.to<=r;)if(!n.parent())return!1;for(;;){if(n.from>=r)return this.fragment.from<=t;if(!n.childAfter(r))return!1}}matches(e){let t=this.cursor.tree;return t&&t.prop(U.contextHash)==e}takeNodes(e){let t=this.cursor,n=this.fragment.offset,r=this.fragmentEnd-(this.fragment.openEnd?1:0),s=e.absoluteLineStart,o=s,l=e.block.children.length,a=o,c=l;for(;;){if(t.to-n>r){if(t.type.isAnonymous&&t.firstChild())continue;break}let h=zS(t.from-n,e.ranges);if(t.to-n<=e.ranges[e.rangeI].to)e.addNode(t.tree,h);else{let f=new J(e.parser.nodeSet.types[A.Paragraph],[],[],0,e.block.hashProp);e.reusePlaceholders.set(f,t.tree),e.addNode(f,h)}if(t.type.is("Block")&&(OA.indexOf(t.type.id)<0?(o=t.to-n,l=e.block.children.length):(o=a,l=c),a=t.to-n,c=e.block.children.length),!t.nextSibling())break}for(;e.block.children.length>l;)e.block.children.pop(),e.block.positions.pop();return o-s}};function zS(i,e){let t=i;for(let n=1;n<e.length;n++){let r=e[n-1].to,s=e[n].from;r<i&&(t-=s-r)}return t}var gA=Bt({"Blockquote/...":m.quote,HorizontalRule:m.contentSeparator,"ATXHeading1/... SetextHeading1/...":m.heading1,"ATXHeading2/... SetextHeading2/...":m.heading2,"ATXHeading3/...":m.heading3,"ATXHeading4/...":m.heading4,"ATXHeading5/...":m.heading5,"ATXHeading6/...":m.heading6,"Comment CommentBlock":m.comment,Escape:m.escape,Entity:m.character,"Emphasis/...":m.emphasis,"StrongEmphasis/...":m.strong,"Link/... Image/...":m.link,"OrderedList/... BulletList/...":m.list,"BlockQuote/...":m.quote,"InlineCode CodeText":m.monospace,"URL Autolink":m.url,"HeaderMark HardBreak QuoteMark ListMark LinkMark EmphasisMark CodeMark":m.processingInstruction,"CodeInfo LinkLabel":m.labelName,LinkTitle:m.string,Paragraph:m.content}),WS=new qo(new zi(jS).extend(gA),Object.keys(qc).map(i=>qc[i]),Object.keys(qc).map(i=>LS[i]),Object.keys(qc),uA,CS,Object.keys(kp).map(i=>kp[i]),Object.keys(kp),[]);function mA(i,e,t){let n=[];for(let r=i.firstChild,s=e;;r=r.nextSibling){let o=r?r.from:t;if(o>s&&n.push({from:s,to:o}),!r)break;s=r.to}return n}function NS(i){let{codeParser:e,htmlParser:t}=i;return{wrap:hc((r,s)=>{let o=r.type.id;if(e&&(o==A.CodeBlock||o==A.FencedCode)){let l="";if(o==A.FencedCode){let c=r.node.getChild(A.CodeInfo);c&&(l=s.read(c.from,c.to))}let a=e(l);if(a)return{parser:a,overlay:c=>c.type.id==A.CodeText,bracketed:o==A.FencedCode}}else if(t&&(o==A.HTMLBlock||o==A.HTMLTag||o==A.CommentBlock))return{parser:t,overlay:mA(r.node,r.from,r.to)};return null})}}var bA={resolve:"Strikethrough",mark:"StrikethroughMark"},yA={defineNodes:[{name:"Strikethrough",style:{"Strikethrough/...":m.strikethrough}},{name:"StrikethroughMark",style:m.processingInstruction}],parseInline:[{name:"Strikethrough",parse(i,e,t){if(e!=126||i.char(t+1)!=126||i.char(t+2)==126)return-1;let n=i.slice(t-1,t),r=i.slice(t+2,t+3),s=/\s|^$/.test(n),o=/\s|^$/.test(r),l=Go.test(n),a=Go.test(r);return i.addDelimiter(bA,t,t+2,!o&&(!a||s||l),!s&&(!l||o||a))},after:"Emphasis"}]};function No(i,e,t=0,n,r=0){let s=0,o=!0,l=-1,a=-1,c=!1,h=()=>{n.push(i.elt("TableCell",r+l,r+a,i.parser.parseInline(e.slice(l,a),r+l)))};for(let f=t;f<e.length;f++){let u=e.charCodeAt(f);u==124&&!c?((!o||l>-1)&&s++,o=!1,n&&(l>-1&&h(),n.push(i.elt("TableDelimiter",f+r,f+r+1))),l=a=-1):(c||u!=32&&u!=9)&&(l<0&&(l=f),a=f+1),c=!c&&u==92}return l>-1&&(s++,n&&h()),s}function xS(i,e){for(let t=e;t<i.length;t++){let n=i.charCodeAt(t);if(n==124)return!0;n==92&&t++}return!1}var qS=/^[>\s]*\|?(\s*:?-+:?\s*\|)+(\s*:?-+:?\s*)?$/,eh=class{constructor(){this.rows=null}nextLine(e,t,n){if(this.rows==null){this.rows=!1;let r;if((t.next==45||t.next==58||t.next==124)&&qS.test(r=t.text.slice(t.pos))){let s=[];No(e,n.content,0,s,n.start)==No(e,r,0)&&(this.rows=[e.elt("TableHeader",n.start,n.start+n.content.length,s),e.elt("TableDelimiter",e.lineStart+t.pos,e.lineStart+t.text.length)])}}else if(this.rows){let r=[];No(e,t.text,t.pos,r,e.lineStart),this.rows.push(e.elt("TableRow",e.lineStart+t.pos,e.lineStart+t.text.length,r))}return!1}finish(e,t){return this.rows?(e.addLeafElement(t,e.elt("Table",t.start,t.start+t.content.length,this.rows)),!0):!1}},SA={defineNodes:[{name:"Table",block:!0},{name:"TableHeader",style:{"TableHeader/...":m.heading}},"TableRow",{name:"TableCell",style:m.content},{name:"TableDelimiter",style:m.processingInstruction}],parseBlock:[{name:"Table",leaf(i,e){return xS(e.content,0)?new eh:null},endLeaf(i,e,t){if(t.parsers.some(r=>r instanceof eh)||!xS(e.text,e.basePos))return!1;let n=i.peekLine();return qS.test(n)&&No(i,e.text,e.basePos)==No(i,n,e.basePos)},before:"SetextHeading"}]},Ep=class{nextLine(){return!1}finish(e,t){return e.addLeafElement(t,e.elt("Task",t.start,t.start+t.content.length,[e.elt("TaskMarker",t.start,t.start+3),...e.parser.parseInline(t.content.slice(3),t.start+3)])),!0}},xA={defineNodes:[{name:"Task",block:!0,style:m.list},{name:"TaskMarker",style:m.atom}],parseBlock:[{name:"TaskList",leaf(i,e){return/^\[[ xX]\][ \t]/.test(e.content)&&i.parentType().name=="ListItem"?new Ep:null},after:"SetextHeading"}]},wS=/(www\.)|(https?:\/\/)|([\w.+-]{1,100}@)|(mailto:|xmpp:)/gy,kS=/[\w-]+(\.[\w-]+)+(:\d+)?(\/[^\s<]*)?/gy,wA=/[\w-]+\.[\w-]+($|[/:])/,QS=/[\w.+-]+@[\w-]+(\.[\w.-]+)+/gy,vS=/\/[a-zA-Z\d@.]+/gy;function $S(i,e,t,n){let r=0;for(let s=e;s<t;s++)i[s]==n&&r++;return r}function kA(i,e){kS.lastIndex=e;let t=kS.exec(i);if(!t||wA.exec(t[0])[0].indexOf("_")>-1)return-1;let n=e+t[0].length;for(;;){let r=i[n-1],s;if(/[?!.,:*_~]/.test(r)||r==")"&&$S(i,e,n,")")>$S(i,e,n,"("))n--;else if(r==";"&&(s=/&(?:#\d+|#x[a-f\d]+|\w+);$/.exec(i.slice(e,n))))n=e+s.index;else break}return n}function PS(i,e){QS.lastIndex=e;let t=QS.exec(i);if(!t)return-1;let n=t[0][t[0].length-1];return n=="_"||n=="-"?-1:e+t[0].length-(n=="."?1:0)}var QA={parseInline:[{name:"Autolink",parse(i,e,t){let n=t-i.offset;if(n&&/\w/.test(i.text[n-1]))return-1;wS.lastIndex=n;let r=wS.exec(i.text),s=-1;if(!r)return-1;if(r[1]||r[2]){if(s=kA(i.text,n+r[0].length),s>-1&&i.hasOpenLink){let o=/([^\[\]]|\[[^\]]*\])*/.exec(i.text.slice(n,s));s=n+o[0].length}}else r[3]?s=PS(i.text,n):(s=PS(i.text,n+r[0].length),s>-1&&r[0]=="xmpp:"&&(vS.lastIndex=s,r=vS.exec(i.text),r&&(s=r.index+r[0].length)));return s<0?-1:(i.addElement(i.elt("URL",t,s+i.offset)),s+i.offset)}}]},GS=[SA,xA,yA,QA];function FS(i,e,t){return(n,r,s)=>{if(r!=i||n.char(s+1)==i)return-1;let o=[n.elt(t,s,s+1)];for(let l=s+1;l<n.end;l++){let a=n.char(l);if(a==i)return n.addElement(n.elt(e,s,l+1,o.concat(n.elt(t,l,l+1))));if(a==92&&o.push(n.elt("Escape",l,l+++2)),Zt(a))break}return-1}}var HS={defineNodes:[{name:"Superscript",style:m.special(m.content)},{name:"SuperscriptMark",style:m.processingInstruction}],parseInline:[{name:"Superscript",parse:FS(94,"Superscript","SuperscriptMark")}]},KS={defineNodes:[{name:"Subscript",style:m.special(m.content)},{name:"SubscriptMark",style:m.processingInstruction}],parseInline:[{name:"Subscript",parse:FS(126,"Subscript","SubscriptMark")}]},JS={defineNodes:[{name:"Emoji",style:m.character}],parseInline:[{name:"Emoji",parse(i,e,t){let n;return e!=58||!(n=/^[a-zA-Z_0-9]+:/.exec(i.slice(t+1,i.end)))?-1:i.addElement(i.elt("Emoji",t,t+1+n[0].length))}}]};var Ip=class i{constructor(e,t,n,r,s,o,l,a,c,h=0,f){this.p=e,this.stack=t,this.state=n,this.reducePos=r,this.pos=s,this.score=o,this.buffer=l,this.bufferBase=a,this.curContext=c,this.lookAhead=h,this.parent=f}toString(){return`[${this.stack.filter((e,t)=>t%3==0).concat(this.state)}]@${this.pos}${this.score?"!"+this.score:""}`}static start(e,t,n=0){let r=e.parser.context;return new i(e,[],t,n,n,0,[],0,r?new th(r,r.start):null,0,null)}get context(){return this.curContext?this.curContext.context:null}pushState(e,t){this.stack.push(this.state,t,this.bufferBase+this.buffer.length),this.state=e}reduce(e){var t;let n=e>>19,r=e&65535,{parser:s}=this.p,o=this.reducePos<this.pos-25&&this.setLookAhead(this.pos),l=s.dynamicPrecedence(r);if(l&&(this.score+=l),n==0){r<s.minRepeatTerm&&this.reducePos<this.pos&&(this.reducePos=this.pos),this.pushState(s.getGoto(this.state,r,!0),this.reducePos),r<s.minRepeatTerm&&this.storeNode(r,this.reducePos,this.reducePos,o?8:4,!0),this.reduceContext(r,this.reducePos);return}let a=this.stack.length-(n-1)*3-(e&262144?6:0),c=a?this.stack[a-2]:this.p.ranges[0].from;r<s.minRepeatTerm&&c==this.reducePos&&this.reducePos<this.pos&&(this.reducePos=this.pos);let h=this.reducePos-c;h>=2e3&&!(!((t=this.p.parser.nodeSet.types[r])===null||t===void 0)&&t.isAnonymous)&&(c==this.p.lastBigReductionStart?(this.p.bigReductionCount++,this.p.lastBigReductionSize=h):this.p.lastBigReductionSize<h&&(this.p.bigReductionCount=1,this.p.lastBigReductionStart=c,this.p.lastBigReductionSize=h));let f=a?this.stack[a-1]:0,u=this.bufferBase+this.buffer.length-f;if(r<s.minRepeatTerm||e&131072){let d=s.stateFlag(this.state,1)?this.pos:this.reducePos;this.storeNode(r,c,d,u+4,!0)}if(e&262144)this.state=this.stack[a];else{let d=this.stack[a-3];this.state=s.getGoto(d,r,!0)}for(;this.stack.length>a;)this.stack.pop();this.reduceContext(r,c)}storeNode(e,t,n,r=4,s=!1){if(e==0&&(!this.stack.length||this.stack[this.stack.length-1]<this.buffer.length+this.bufferBase)){let o=this.buffer.length;if(o>0&&this.buffer[o-4]==0&&this.buffer[o-1]>-1){if(t==n)return;if(this.buffer[o-2]>=t){this.buffer[o-2]=n;return}}}if(!s||this.pos==n)this.buffer.push(e,t,n,r);else{let o=this.buffer.length;if(o>0&&(this.buffer[o-4]!=0||this.buffer[o-1]<0)){let l=!1;for(let a=o;a>0&&this.buffer[a-2]>n;a-=4)if(this.buffer[a-1]>=0){l=!0;break}if(l)for(;o>0&&this.buffer[o-2]>n;)this.buffer[o]=this.buffer[o-4],this.buffer[o+1]=this.buffer[o-3],this.buffer[o+2]=this.buffer[o-2],this.buffer[o+3]=this.buffer[o-1],o-=4,r>4&&(r-=4)}this.buffer[o]=e,this.buffer[o+1]=t,this.buffer[o+2]=n,this.buffer[o+3]=r}}shift(e,t,n,r){if(e&131072)this.pushState(e&65535,this.pos);else if((e&262144)==0){let s=e,{parser:o}=this.p;this.pos=r;let l=o.stateFlag(s,1);!l&&(r>n||t<=o.maxNode)&&(this.reducePos=r),this.pushState(s,l?n:Math.min(n,this.reducePos)),this.shiftContext(t,n),t<=o.maxNode&&this.buffer.push(t,n,r,4)}else this.pos=r,this.shiftContext(t,n),t<=this.p.parser.maxNode&&this.buffer.push(t,n,r,4)}apply(e,t,n,r){e&65536?this.reduce(e):this.shift(e,t,n,r)}useNode(e,t){let n=this.p.reused.length-1;(n<0||this.p.reused[n]!=e)&&(this.p.reused.push(e),n++);let r=this.pos;this.reducePos=this.pos=r+e.length,this.pushState(t,r),this.buffer.push(n,r,this.reducePos,-1),this.curContext&&this.updateContext(this.curContext.tracker.reuse(this.curContext.context,e,this,this.p.stream.reset(this.pos-e.length)))}split(){let e=this,t=e.buffer.length;for(t&&e.buffer[t-4]==0&&(t-=4);t>0&&e.buffer[t-2]>e.reducePos;)t-=4;let n=e.buffer.slice(t),r=e.bufferBase+t;for(;e&&r==e.bufferBase;)e=e.parent;return new i(this.p,this.stack.slice(),this.state,this.reducePos,this.pos,this.score,n,r,this.curContext,this.lookAhead,e)}recoverByDelete(e,t){let n=e<=this.p.parser.maxNode;n&&this.storeNode(e,this.pos,t,4),this.storeNode(0,this.pos,t,n?8:4),this.pos=this.reducePos=t,this.score-=190}canShift(e){for(let t=new Up(this);;){let n=this.p.parser.stateSlot(t.state,4)||this.p.parser.hasAction(t.state,e);if(n==0)return!1;if((n&65536)==0)return!0;t.reduce(n)}}recoverByInsert(e){if(this.stack.length>=300)return[];let t=this.p.parser.nextStates(this.state);if(t.length>8||this.stack.length>=120){let r=[];for(let s=0,o;s<t.length;s+=2)(o=t[s+1])!=this.state&&this.p.parser.hasAction(o,e)&&r.push(t[s],o);if(this.stack.length<120)for(let s=0;r.length<8&&s<t.length;s+=2){let o=t[s+1];r.some((l,a)=>a&1&&l==o)||r.push(t[s],o)}t=r}let n=[];for(let r=0;r<t.length&&n.length<4;r+=2){let s=t[r+1];if(s==this.state)continue;let o=this.split();o.pushState(s,this.pos),o.storeNode(0,o.pos,o.pos,4,!0),o.shiftContext(t[r],this.pos),o.reducePos=this.pos,o.score-=200,n.push(o)}return n}forceReduce(){let{parser:e}=this.p,t=e.stateSlot(this.state,5);if((t&65536)==0)return!1;if(!e.validAction(this.state,t)){let n=t>>19,r=t&65535,s=this.stack.length-n*3;if(s<0||e.getGoto(this.stack[s],r,!1)<0){let o=this.findForcedReduction();if(o==null)return!1;t=o}this.storeNode(0,this.pos,this.pos,4,!0),this.score-=100}return this.reducePos=this.pos,this.reduce(t),!0}findForcedReduction(){let{parser:e}=this.p,t=[],n=(r,s)=>{if(!t.includes(r))return t.push(r),e.allActions(r,o=>{if(!(o&393216))if(o&65536){let l=(o>>19)-s;if(l>1){let a=o&65535,c=this.stack.length-l*3;if(c>=0&&e.getGoto(this.stack[c],a,!1)>=0)return l<<19|65536|a}}else{let l=n(o,s+1);if(l!=null)return l}})};return n(this.state,0)}forceAll(){for(;!this.p.parser.stateFlag(this.state,2);)if(!this.forceReduce()){this.storeNode(0,this.pos,this.pos,4,!0);break}return this}get deadEnd(){if(this.stack.length!=3)return!1;let{parser:e}=this.p;return e.data[e.stateSlot(this.state,1)]==65535&&!e.stateSlot(this.state,4)}restart(){this.storeNode(0,this.pos,this.pos,4,!0),this.state=this.stack[0],this.stack.length=0}sameState(e){if(this.state!=e.state||this.stack.length!=e.stack.length)return!1;for(let t=0;t<this.stack.length;t+=3)if(this.stack[t]!=e.stack[t])return!1;return!0}get parser(){return this.p.parser}dialectEnabled(e){return this.p.parser.dialect.flags[e]}shiftContext(e,t){this.curContext&&this.updateContext(this.curContext.tracker.shift(this.curContext.context,e,this,this.p.stream.reset(t)))}reduceContext(e,t){this.curContext&&this.updateContext(this.curContext.tracker.reduce(this.curContext.context,e,this,this.p.stream.reset(t)))}emitContext(){let e=this.buffer.length-1;(e<0||this.buffer[e]!=-3)&&this.buffer.push(this.curContext.hash,this.pos,this.pos,-3)}emitLookAhead(){let e=this.buffer.length-1;(e<0||this.buffer[e]!=-4)&&this.buffer.push(this.lookAhead,this.pos,this.pos,-4)}updateContext(e){if(e!=this.curContext.context){let t=new th(this.curContext.tracker,e);t.hash!=this.curContext.hash&&this.emitContext(),this.curContext=t}}setLookAhead(e){return e<=this.lookAhead?!1:(this.emitLookAhead(),this.lookAhead=e,!0)}close(){this.curContext&&this.curContext.tracker.strict&&this.emitContext(),this.lookAhead>0&&this.emitLookAhead()}},th=class{constructor(e,t){this.tracker=e,this.context=t,this.hash=e.strict?e.hash(t):0}},Up=class{constructor(e){this.start=e,this.state=e.state,this.stack=e.stack,this.base=this.stack.length}reduce(e){let t=e&65535,n=e>>19;n==0?(this.stack==this.start.stack&&(this.stack=this.stack.slice()),this.stack.push(this.state,0,0),this.base+=3):this.base-=(n-1)*3;let r=this.start.p.parser.getGoto(this.stack[this.base-3],t,!0);this.state=r}},Bp=class i{constructor(e,t,n){this.stack=e,this.pos=t,this.index=n,this.buffer=e.buffer,this.index==0&&this.maybeNext()}static create(e,t=e.bufferBase+e.buffer.length){return new i(e,t,t-e.bufferBase)}maybeNext(){let e=this.stack.parent;e!=null&&(this.index=this.stack.bufferBase-e.bufferBase,this.stack=e,this.buffer=e.buffer)}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}next(){this.index-=4,this.pos-=4,this.index==0&&this.maybeNext()}fork(){return new i(this.stack,this.pos,this.index)}};function Ho(i,e=Uint16Array){if(typeof i!="string")return i;let t=null;for(let n=0,r=0;n<i.length;){let s=0;for(;;){let o=i.charCodeAt(n++),l=!1;if(o==126){s=65535;break}o>=92&&o--,o>=34&&o--;let a=o-32;if(a>=46&&(a-=46,l=!0),s+=a,l)break;s*=46}t?t[r++]=s:t=new e(s)}return t}var ps=class{constructor(){this.start=-1,this.value=-1,this.end=-1,this.extended=-1,this.lookAhead=0,this.mask=0,this.context=0}},ex=new ps,Vp=class{constructor(e,t){this.input=e,this.ranges=t,this.chunk="",this.chunkOff=0,this.chunk2="",this.chunk2Pos=0,this.next=-1,this.token=ex,this.rangeIndex=0,this.pos=this.chunkPos=t[0].from,this.range=t[0],this.end=t[t.length-1].to,this.readNext()}resolveOffset(e,t){let n=this.range,r=this.rangeIndex,s=this.pos+e;for(;s<n.from;){if(!r)return null;let o=this.ranges[--r];s-=n.from-o.to,n=o}for(;t<0?s>n.to:s>=n.to;){if(r==this.ranges.length-1)return null;let o=this.ranges[++r];s+=o.from-n.to,n=o}return s}clipPos(e){if(e>=this.range.from&&e<this.range.to)return e;for(let t of this.ranges)if(t.to>e)return Math.max(e,t.from);return this.end}peek(e){let t=this.chunkOff+e,n,r;if(t>=0&&t<this.chunk.length)n=this.pos+e,r=this.chunk.charCodeAt(t);else{let s=this.resolveOffset(e,1);if(s==null)return-1;if(n=s,n>=this.chunk2Pos&&n<this.chunk2Pos+this.chunk2.length)r=this.chunk2.charCodeAt(n-this.chunk2Pos);else{let o=this.rangeIndex,l=this.range;for(;l.to<=n;)l=this.ranges[++o];this.chunk2=this.input.chunk(this.chunk2Pos=n),n+this.chunk2.length>l.to&&(this.chunk2=this.chunk2.slice(0,l.to-n)),r=this.chunk2.charCodeAt(0)}}return n>=this.token.lookAhead&&(this.token.lookAhead=n+1),r}acceptToken(e,t=0){let n=t?this.resolveOffset(t,-1):this.pos;if(n==null||n<this.token.start)throw new RangeError("Token end out of bounds");this.token.value=e,this.token.end=n}acceptTokenTo(e,t){this.token.value=e,this.token.end=t}getChunk(){if(this.pos>=this.chunk2Pos&&this.pos<this.chunk2Pos+this.chunk2.length){let{chunk:e,chunkPos:t}=this;this.chunk=this.chunk2,this.chunkPos=this.chunk2Pos,this.chunk2=e,this.chunk2Pos=t,this.chunkOff=this.pos-this.chunkPos}else{this.chunk2=this.chunk,this.chunk2Pos=this.chunkPos;let e=this.input.chunk(this.pos),t=this.pos+e.length;this.chunk=t>this.range.to?e.slice(0,this.range.to-this.pos):e,this.chunkPos=this.pos,this.chunkOff=0}}readNext(){return this.chunkOff>=this.chunk.length&&(this.getChunk(),this.chunkOff==this.chunk.length)?this.next=-1:this.next=this.chunk.charCodeAt(this.chunkOff)}advance(e=1){for(this.chunkOff+=e;this.pos+e>=this.range.to;){if(this.rangeIndex==this.ranges.length-1)return this.setDone();e-=this.range.to-this.pos,this.range=this.ranges[++this.rangeIndex],this.pos=this.range.from}return this.pos+=e,this.pos>=this.token.lookAhead&&(this.token.lookAhead=this.pos+1),this.readNext()}setDone(){return this.pos=this.chunkPos=this.end,this.range=this.ranges[this.rangeIndex=this.ranges.length-1],this.chunk="",this.next=-1}reset(e,t){if(t?(this.token=t,t.start=e,t.lookAhead=e+1,t.value=t.extended=-1):this.token=ex,this.pos!=e){if(this.pos=e,e==this.end)return this.setDone(),this;for(;e<this.range.from;)this.range=this.ranges[--this.rangeIndex];for(;e>=this.range.to;)this.range=this.ranges[++this.rangeIndex];e>=this.chunkPos&&e<this.chunkPos+this.chunk.length?this.chunkOff=e-this.chunkPos:(this.chunk="",this.chunkOff=0),this.readNext()}return this}read(e,t){if(e>=this.chunkPos&&t<=this.chunkPos+this.chunk.length)return this.chunk.slice(e-this.chunkPos,t-this.chunkPos);if(e>=this.chunk2Pos&&t<=this.chunk2Pos+this.chunk2.length)return this.chunk2.slice(e-this.chunk2Pos,t-this.chunk2Pos);if(e>=this.range.from&&t<=this.range.to)return this.input.read(e,t);let n="";for(let r of this.ranges){if(r.from>=t)break;r.to>e&&(n+=this.input.read(Math.max(r.from,e),Math.min(r.to,t)))}return n}},on=class{constructor(e,t){this.data=e,this.id=t}token(e,t){let{parser:n}=t.p;sx(this.data,e,t,this.id,n.data,n.tokenPrecTable)}};on.prototype.contextual=on.prototype.fallback=on.prototype.extend=!1;var ln=class{constructor(e,t,n){this.precTable=t,this.elseToken=n,this.data=typeof e=="string"?Ho(e):e}token(e,t){let n=e.pos,r=0;for(;;){let s=e.next<0,o=e.resolveOffset(1,1);if(sx(this.data,e,t,0,this.data,this.precTable),e.token.value>-1)break;if(this.elseToken==null)return;if(s||r++,o==null)break;e.reset(o,e.token)}r&&(e.reset(n,e.token),e.acceptToken(this.elseToken,r))}};ln.prototype.contextual=on.prototype.fallback=on.prototype.extend=!1;var Me=class{constructor(e,t={}){this.token=e,this.contextual=!!t.contextual,this.fallback=!!t.fallback,this.extend=!!t.extend}};function sx(i,e,t,n,r,s){let o=0,l=1<<n,{dialect:a}=t.p.parser;e:for(;(l&i[o])!=0;){let c=i[o+1];for(let d=o+3;d<c;d+=2)if((i[d+1]&l)>0){let p=i[d];if(a.allows(p)&&(e.token.value==-1||e.token.value==p||$A(p,e.token.value,r,s))){e.acceptToken(p);break}}let h=e.next,f=0,u=i[o+2];if(e.next<0&&u>f&&i[c+u*3-3]==65535){o=i[c+u*3-1];continue e}for(;f<u;){let d=f+u>>1,p=c+d+(d<<1),O=i[p],g=i[p+1]||65536;if(h<O)u=d;else if(h>=g)f=d+1;else{o=i[p+2],e.advance();continue e}}break}}function tx(i,e,t){for(let n=e,r;(r=i[n])!=65535;n++)if(r==t)return n-e;return-1}function $A(i,e,t,n){let r=tx(t,n,e);return r<0||tx(t,n,i)<r}var kt=typeof process<"u"&&process.env&&/\bparse\b/.test(process.env.LOG),Dp=null;function ix(i,e,t){let n=i.cursor(F.IncludeAnonymous);for(n.moveTo(e);;)if(!(t<0?n.childBefore(e):n.childAfter(e)))for(;;){if((t<0?n.to<e:n.from>e)&&!n.type.isError)return t<0?Math.max(0,Math.min(n.to-1,e-25)):Math.min(i.length,Math.max(n.from+1,e+25));if(t<0?n.prevSibling():n.nextSibling())break;if(!n.parent())return t<0?0:i.length}}var Yp=class{constructor(e,t){this.fragments=e,this.nodeSet=t,this.i=0,this.fragment=null,this.safeFrom=-1,this.safeTo=-1,this.trees=[],this.start=[],this.index=[],this.nextFragment()}nextFragment(){let e=this.fragment=this.i==this.fragments.length?null:this.fragments[this.i++];if(e){for(this.safeFrom=e.openStart?ix(e.tree,e.from+e.offset,1)-e.offset:e.from,this.safeTo=e.openEnd?ix(e.tree,e.to+e.offset,-1)-e.offset:e.to;this.trees.length;)this.trees.pop(),this.start.pop(),this.index.pop();this.trees.push(e.tree),this.start.push(-e.offset),this.index.push(0),this.nextStart=this.safeFrom}else this.nextStart=1e9}nodeAt(e){if(e<this.nextStart)return null;for(;this.fragment&&this.safeTo<=e;)this.nextFragment();if(!this.fragment)return null;for(;;){let t=this.trees.length-1;if(t<0)return this.nextFragment(),null;let n=this.trees[t],r=this.index[t];if(r==n.children.length){this.trees.pop(),this.start.pop(),this.index.pop();continue}let s=n.children[r],o=this.start[t]+n.positions[r];if(o>e)return this.nextStart=o,null;if(s instanceof J){if(o==e){if(o<this.safeFrom)return null;let l=o+s.length;if(l<=this.safeTo){let a=s.prop(U.lookAhead);if(!a||l+a<this.fragment.to)return s}}this.index[t]++,o+s.length>=Math.max(this.safeFrom,e)&&(this.trees.push(s),this.start.push(o),this.index.push(0))}else this.index[t]++,this.nextStart=o+s.length}}},zp=class{constructor(e,t){this.stream=t,this.tokens=[],this.mainToken=null,this.actions=[],this.tokens=e.tokenizers.map(n=>new ps)}getActions(e){let t=0,n=null,{parser:r}=e.p,{tokenizers:s}=r,o=r.stateSlot(e.state,3),l=e.curContext?e.curContext.hash:0,a=0;for(let c=0;c<s.length;c++){if((1<<c&o)==0)continue;let h=s[c],f=this.tokens[c];if(!(n&&!h.fallback)&&((h.contextual||f.start!=e.pos||f.mask!=o||f.context!=l)&&(this.updateCachedToken(f,h,e),f.mask=o,f.context=l),f.lookAhead>f.end+25&&(a=Math.max(f.lookAhead,a)),f.value!=0)){let u=t;if(f.extended>-1&&(t=this.addActions(e,f.extended,f.end,t)),t=this.addActions(e,f.value,f.end,t),!h.extend&&(n=f,t>u))break}}for(;this.actions.length>t;)this.actions.pop();return a&&e.setLookAhead(a),!n&&e.pos==this.stream.end&&(n=new ps,n.value=e.p.parser.eofTerm,n.start=n.end=e.pos,t=this.addActions(e,n.value,n.end,t)),this.mainToken=n,this.actions}getMainToken(e){if(this.mainToken)return this.mainToken;let t=new ps,{pos:n,p:r}=e;return t.start=n,t.end=Math.min(n+1,r.stream.end),t.value=n==r.stream.end?r.parser.eofTerm:0,t}updateCachedToken(e,t,n){let r=this.stream.clipPos(n.pos);if(t.token(this.stream.reset(r,e),n),e.value>-1){let{parser:s}=n.p;for(let o=0;o<s.specialized.length;o++)if(s.specialized[o]==e.value){let l=s.specializers[o](this.stream.read(e.start,e.end),n);if(l>=0&&n.p.parser.dialect.allows(l>>1)){(l&1)==0?e.value=l>>1:e.extended=l>>1;break}}}else e.value=0,e.end=this.stream.clipPos(r+1)}putAction(e,t,n,r){for(let s=0;s<r;s+=3)if(this.actions[s]==e)return r;return this.actions[r++]=e,this.actions[r++]=t,this.actions[r++]=n,r}addActions(e,t,n,r){let{state:s}=e,{parser:o}=e.p,{data:l}=o;for(let a=0;a<2;a++)for(let c=o.stateSlot(s,a?2:1);;c+=3){if(l[c]==65535)if(l[c+1]==1)c=Ci(l,c+2);else{r==0&&l[c+1]==2&&(r=this.putAction(Ci(l,c+2),t,n,r));break}l[c]==t&&(r=this.putAction(Ci(l,c+1),t,n,r))}return r}},Wp=class{constructor(e,t,n,r){this.parser=e,this.input=t,this.ranges=r,this.recovering=0,this.nextStackID=9812,this.minStackPos=0,this.reused=[],this.stoppedAt=null,this.lastBigReductionStart=-1,this.lastBigReductionSize=0,this.bigReductionCount=0,this.stream=new Vp(t,r),this.tokens=new zp(e,this.stream),this.topTerm=e.top[1];let{from:s}=r[0];this.stacks=[Ip.start(this,e.top[0],s)],this.fragments=n.length&&this.stream.end-s>e.bufferLength*4?new Yp(n,e.nodeSet):null}get parsedPos(){return this.minStackPos}advance(){let e=this.stacks,t=this.minStackPos,n=this.stacks=[],r,s;if(this.bigReductionCount>300&&e.length==1){let[o]=e;for(;o.forceReduce()&&o.stack.length&&o.stack[o.stack.length-2]>=this.lastBigReductionStart;);this.bigReductionCount=this.lastBigReductionSize=0}for(let o=0;o<e.length;o++){let l=e[o];for(;;){if(this.tokens.mainToken=null,l.pos>t)n.push(l);else{if(this.advanceStack(l,n,e))continue;{r||(r=[],s=[]),r.push(l);let a=this.tokens.getMainToken(l);s.push(a.value,a.end)}}break}}if(!n.length){let o=r&&PA(r);if(o)return kt&&console.log("Finish with "+this.stackID(o)),this.stackToTree(o);if(this.parser.strict)throw kt&&r&&console.log("Stuck with token "+(this.tokens.mainToken?this.parser.getName(this.tokens.mainToken.value):"none")),new SyntaxError("No parse at "+t);this.recovering||(this.recovering=5)}if(this.recovering&&r){let o=this.stoppedAt!=null&&r[0].pos>this.stoppedAt?r[0]:this.runRecovery(r,s,n);if(o)return kt&&console.log("Force-finish "+this.stackID(o)),this.stackToTree(o.forceAll())}if(this.recovering){let o=this.recovering==1?1:this.recovering*3;if(n.length>o)for(n.sort((l,a)=>a.score-l.score);n.length>o;)n.pop();n.some(l=>l.reducePos>t)&&this.recovering--}else if(n.length>1){e:for(let o=0;o<n.length-1;o++){let l=n[o];for(let a=o+1;a<n.length;a++){let c=n[a];if(l.sameState(c)||l.buffer.length>500&&c.buffer.length>500)if((l.score-c.score||l.buffer.length-c.buffer.length)>0)n.splice(a--,1);else{n.splice(o--,1);continue e}}}n.length>12&&(n.sort((o,l)=>l.score-o.score),n.splice(12,n.length-12))}this.minStackPos=n[0].pos;for(let o=1;o<n.length;o++)n[o].pos<this.minStackPos&&(this.minStackPos=n[o].pos);return null}stopAt(e){if(this.stoppedAt!=null&&this.stoppedAt<e)throw new RangeError("Can't move stoppedAt forward");this.stoppedAt=e}advanceStack(e,t,n){let r=e.pos,{parser:s}=this,o=kt?this.stackID(e)+" -> ":"";if(this.stoppedAt!=null&&r>this.stoppedAt)return e.forceReduce()?e:null;if(this.fragments){let c=e.curContext&&e.curContext.tracker.strict,h=c?e.curContext.hash:0;for(let f=this.fragments.nodeAt(r);f;){let u=this.parser.nodeSet.types[f.type.id]==f.type?s.getGoto(e.state,f.type.id):-1;if(u>-1&&f.length&&(!c||(f.prop(U.contextHash)||0)==h))return e.useNode(f,u),kt&&console.log(o+this.stackID(e)+` (via reuse of ${s.getName(f.type.id)})`),!0;if(!(f instanceof J)||f.children.length==0||f.positions[0]>0)break;let d=f.children[0];if(d instanceof J&&f.positions[0]==0)f=d;else break}}let l=s.stateSlot(e.state,4);if(l>0)return e.reduce(l),kt&&console.log(o+this.stackID(e)+` (via always-reduce ${s.getName(l&65535)})`),!0;if(e.stack.length>=8400)for(;e.stack.length>6e3&&e.forceReduce(););let a=this.tokens.getActions(e);for(let c=0;c<a.length;){let h=a[c++],f=a[c++],u=a[c++],d=c==a.length||!n,p=d?e:e.split(),O=this.tokens.mainToken;if(p.apply(h,f,O?O.start:p.pos,u),kt&&console.log(o+this.stackID(p)+` (via ${(h&65536)==0?"shift":`reduce of ${s.getName(h&65535)}`} for ${s.getName(f)} @ ${r}${p==e?"":", split"})`),d)return!0;p.pos>r?t.push(p):n.push(p)}return!1}advanceFully(e,t){let n=e.pos;for(;;){if(!this.advanceStack(e,null,null))return!1;if(e.pos>n)return nx(e,t),!0}}runRecovery(e,t,n){let r=null,s=!1;for(let o=0;o<e.length;o++){let l=e[o],a=t[o<<1],c=t[(o<<1)+1],h=kt?this.stackID(l)+" -> ":"";if(l.deadEnd&&(s||(s=!0,l.restart(),kt&&console.log(h+this.stackID(l)+" (restarted)"),this.advanceFully(l,n))))continue;let f=l.split(),u=h;for(let d=0;d<10&&f.forceReduce()&&(kt&&console.log(u+this.stackID(f)+" (via force-reduce)"),!this.advanceFully(f,n));d++)kt&&(u=this.stackID(f)+" -> ");for(let d of l.recoverByInsert(a))kt&&console.log(h+this.stackID(d)+" (via recover-insert)"),this.advanceFully(d,n);this.stream.end>l.pos?(c==l.pos&&(c++,a=0),l.recoverByDelete(a,c),kt&&console.log(h+this.stackID(l)+` (via recover-delete ${this.parser.getName(a)})`),nx(l,n)):(!r||r.score<f.score)&&(r=f)}return r}stackToTree(e){return e.close(),J.build({buffer:Bp.create(e),nodeSet:this.parser.nodeSet,topID:this.topTerm,maxBufferLength:this.parser.bufferLength,reused:this.reused,start:this.ranges[0].from,length:e.pos-this.ranges[0].from,minRepeatType:this.parser.minRepeatTerm})}stackID(e){let t=(Dp||(Dp=new WeakMap)).get(e);return t||Dp.set(e,t=String.fromCodePoint(this.nextStackID++)),t+e}};function nx(i,e){for(let t=0;t<e.length;t++){let n=e[t];if(n.pos==i.pos&&n.sameState(i)){e[t].score<i.score&&(e[t]=i);return}}e.push(i)}var Np=class{constructor(e,t,n){this.source=e,this.flags=t,this.disabled=n}allows(e){return!this.disabled||this.disabled[e]==0}},jp=i=>i,Os=class{constructor(e){this.start=e.start,this.shift=e.shift||jp,this.reduce=e.reduce||jp,this.reuse=e.reuse||jp,this.hash=e.hash||(()=>0),this.strict=e.strict!==!1}},an=class i extends Ni{constructor(e){if(super(),this.wrappers=[],e.version!=14)throw new RangeError(`Parser version (${e.version}) doesn't match runtime version (14)`);let t=e.nodeNames.split(" ");this.minRepeatTerm=t.length;for(let l=0;l<e.repeatNodeCount;l++)t.push("");let n=Object.keys(e.topRules).map(l=>e.topRules[l][1]),r=[];for(let l=0;l<t.length;l++)r.push([]);function s(l,a,c){r[l].push([a,a.deserialize(String(c))])}if(e.nodeProps)for(let l of e.nodeProps){let a=l[0];typeof a=="string"&&(a=U[a]);for(let c=1;c<l.length;){let h=l[c++];if(h>=0)s(h,a,l[c++]);else{let f=l[c+-h];for(let u=-h;u>0;u--)s(l[c++],a,f);c++}}}this.nodeSet=new zi(t.map((l,a)=>Oe.define({name:a>=this.minRepeatTerm?void 0:l,id:a,props:r[a],top:n.indexOf(a)>-1,error:a==0,skipped:e.skippedNodes&&e.skippedNodes.indexOf(a)>-1}))),e.propSources&&(this.nodeSet=this.nodeSet.extend(...e.propSources)),this.strict=!1,this.bufferLength=1024;let o=Ho(e.tokenData);this.context=e.context,this.specializerSpecs=e.specialized||[],this.specialized=new Uint16Array(this.specializerSpecs.length);for(let l=0;l<this.specializerSpecs.length;l++)this.specialized[l]=this.specializerSpecs[l].term;this.specializers=this.specializerSpecs.map(rx),this.states=Ho(e.states,Uint32Array),this.data=Ho(e.stateData),this.goto=Ho(e.goto),this.maxTerm=e.maxTerm,this.tokenizers=e.tokenizers.map(l=>typeof l=="number"?new on(o,l):l),this.topRules=e.topRules,this.dialects=e.dialects||{},this.dynamicPrecedences=e.dynamicPrecedences||null,this.tokenPrecTable=e.tokenPrec,this.termNames=e.termNames||null,this.maxNode=this.nodeSet.types.length-1,this.dialect=this.parseDialect(),this.top=this.topRules[Object.keys(this.topRules)[0]]}createParse(e,t,n){let r=new Wp(this,e,t,n);for(let s of this.wrappers)r=s(r,e,t,n);return r}getGoto(e,t,n=!1){let r=this.goto;if(t>=r[0])return-1;for(let s=r[t+1];;){let o=r[s++],l=o&1,a=r[s++];if(l&&n)return a;for(let c=s+(o>>1);s<c;s++)if(r[s]==e)return a;if(l)return-1}}hasAction(e,t){let n=this.data;for(let r=0;r<2;r++)for(let s=this.stateSlot(e,r?2:1),o;;s+=3){if((o=n[s])==65535)if(n[s+1]==1)o=n[s=Ci(n,s+2)];else{if(n[s+1]==2)return Ci(n,s+2);break}if(o==t||o==0)return Ci(n,s+1)}return 0}stateSlot(e,t){return this.states[e*6+t]}stateFlag(e,t){return(this.stateSlot(e,0)&t)>0}validAction(e,t){return!!this.allActions(e,n=>n==t?!0:null)}allActions(e,t){let n=this.stateSlot(e,4),r=n?t(n):void 0;for(let s=this.stateSlot(e,1);r==null;s+=3){if(this.data[s]==65535)if(this.data[s+1]==1)s=Ci(this.data,s+2);else break;r=t(Ci(this.data,s+1))}return r}nextStates(e){let t=[];for(let n=this.stateSlot(e,1);;n+=3){if(this.data[n]==65535)if(this.data[n+1]==1)n=Ci(this.data,n+2);else break;if((this.data[n+2]&1)==0){let r=this.data[n+1];t.some((s,o)=>o&1&&s==r)||t.push(this.data[n],r)}}return t}configure(e){let t=Object.assign(Object.create(i.prototype),this);if(e.props&&(t.nodeSet=this.nodeSet.extend(...e.props)),e.top){let n=this.topRules[e.top];if(!n)throw new RangeError(`Invalid top rule name ${e.top}`);t.top=n}return e.tokenizers&&(t.tokenizers=this.tokenizers.map(n=>{let r=e.tokenizers.find(s=>s.from==n);return r?r.to:n})),e.specializers&&(t.specializers=this.specializers.slice(),t.specializerSpecs=this.specializerSpecs.map((n,r)=>{let s=e.specializers.find(l=>l.from==n.external);if(!s)return n;let o=Object.assign(Object.assign({},n),{external:s.to});return t.specializers[r]=rx(o),o})),e.contextTracker&&(t.context=e.contextTracker),e.dialect&&(t.dialect=this.parseDialect(e.dialect)),e.strict!=null&&(t.strict=e.strict),e.wrap&&(t.wrappers=t.wrappers.concat(e.wrap)),e.bufferLength!=null&&(t.bufferLength=e.bufferLength),t}hasWrappers(){return this.wrappers.length>0}getName(e){return this.termNames?this.termNames[e]:String(e<=this.maxNode&&this.nodeSet.types[e].name||e)}get eofTerm(){return this.maxNode+1}get topNode(){return this.nodeSet.types[this.top[1]]}dynamicPrecedence(e){let t=this.dynamicPrecedences;return t==null?0:t[e]||0}parseDialect(e){let t=Object.keys(this.dialects),n=t.map(()=>!1);if(e)for(let s of e.split(" ")){let o=t.indexOf(s);o>=0&&(n[o]=!0)}let r=null;for(let s=0;s<t.length;s++)if(!n[s])for(let o=this.dialects[t[s]],l;(l=this.data[o++])!=65535;)(r||(r=new Uint8Array(this.maxTerm+1)))[l]=1;return new Np(e,n,r)}static deserialize(e){return new i(e)}};function Ci(i,e){return i[e]|i[e+1]<<16}function PA(i){let e=null;for(let t of i){let n=t.p.stoppedAt;(t.pos==t.p.stream.end||n!=null&&t.pos>n)&&t.p.parser.stateFlag(t.state,2)&&(!e||e.score<t.score)&&(e=t)}return e}function rx(i){if(i.external){let e=i.extend?1:0;return(t,n)=>i.external(t,n)<<1|e}return i.get}var CA=55,TA=1,AA=56,MA=2,RA=57,XA=3,ox=4,EA=5,Kp=6,px=7,Ox=8,gx=9,mx=10,ZA=11,_A=12,LA=13,qp=58,DA=14,jA=15,lx=59,bx=21,IA=23,yx=24,UA=25,Fp=27,Sx=28,BA=29,VA=32,YA=35,zA=37,WA=38,NA=0,qA=1,GA={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},FA={dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},ax={dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}};function HA(i){return i==45||i==46||i==58||i>=65&&i<=90||i==95||i>=97&&i<=122||i>=161}var cx=null,hx=null,fx=0;function Hp(i,e){let t=i.pos+e;if(fx==t&&hx==i)return cx;let n=i.peek(e),r="";for(;HA(n);)r+=String.fromCharCode(n),n=i.peek(++e);return hx=i,fx=t,cx=r?r.toLowerCase():n==KA||n==JA?void 0:null}var xx=60,ih=62,Jp=47,KA=63,JA=33,eM=45;function ux(i,e){this.name=i,this.parent=e}var tM=[Kp,mx,px,Ox,gx],iM=new Os({start:null,shift(i,e,t,n){return tM.indexOf(e)>-1?new ux(Hp(n,1)||"",i):i},reduce(i,e){return e==bx&&i?i.parent:i},reuse(i,e,t,n){let r=e.type.id;return r==Kp||r==zA?new ux(Hp(n,1)||"",i):i},strict:!1}),nM=new Me((i,e)=>{if(i.next!=xx){i.next<0&&e.context&&i.acceptToken(qp);return}i.advance();let t=i.next==Jp;t&&i.advance();let n=Hp(i,0);if(n===void 0)return;if(!n)return i.acceptToken(t?jA:DA);let r=e.context?e.context.name:null;if(t){if(n==r)return i.acceptToken(ZA);if(r&&FA[r])return i.acceptToken(qp,-2);if(e.dialectEnabled(NA))return i.acceptToken(_A);for(let s=e.context;s;s=s.parent)if(s.name==n)return;i.acceptToken(LA)}else{if(n=="script")return i.acceptToken(px);if(n=="style")return i.acceptToken(Ox);if(n=="textarea")return i.acceptToken(gx);if(GA.hasOwnProperty(n))return i.acceptToken(mx);r&&ax[r]&&ax[r][n]?i.acceptToken(qp,-1):i.acceptToken(Kp)}},{contextual:!0}),rM=new Me(i=>{for(let e=0,t=0;;t++){if(i.next<0){t&&i.acceptToken(lx);break}if(i.next==eM)e++;else if(i.next==ih&&e>=2){t>=3&&i.acceptToken(lx,-2);break}else e=0;i.advance()}});function sM(i){for(;i;i=i.parent)if(i.name=="svg"||i.name=="math")return!0;return!1}var oM=new Me((i,e)=>{if(i.next==Jp&&i.peek(1)==ih){let t=e.dialectEnabled(qA)||sM(e.context);i.acceptToken(t?EA:ox,2)}else i.next==ih&&i.acceptToken(ox,1)});function eO(i,e,t){let n=2+i.length;return new Me(r=>{for(let s=0,o=0,l=0;;l++){if(r.next<0){l&&r.acceptToken(e);break}if(s==0&&r.next==xx||s==1&&r.next==Jp||s>=2&&s<n&&r.next==i.charCodeAt(s-2))s++,o++;else if(s==n&&r.next==ih){l>o?r.acceptToken(e,-o):r.acceptToken(t,-(o-2));break}else if((r.next==10||r.next==13)&&l){r.acceptToken(e,1);break}else s=o=0;r.advance()}})}var lM=eO("script",CA,TA),aM=eO("style",AA,MA),cM=eO("textarea",RA,XA),hM=Bt({"Text RawText IncompleteTag IncompleteCloseTag":m.content,"StartTag StartCloseTag SelfClosingEndTag EndTag":m.angleBracket,TagName:m.tagName,"MismatchedCloseTag/TagName":[m.tagName,m.invalid],AttributeName:m.attributeName,"AttributeValue UnquotedAttributeValue":m.attributeValue,Is:m.definitionOperator,"EntityReference CharacterReference":m.character,Comment:m.blockComment,ProcessingInst:m.processingInstruction,DoctypeDecl:m.documentMeta}),wx=an.deserialize({version:14,states:",xOVO!rOOO!ZQ#tO'#CrO!`Q#tO'#C{O!eQ#tO'#DOO!jQ#tO'#DRO!oQ#tO'#DTO!tOaO'#CqO#PObO'#CqO#[OdO'#CqO$kO!rO'#CqOOO`'#Cq'#CqO$rO$fO'#DUO$zQ#tO'#DWO%PQ#tO'#DXOOO`'#Dl'#DlOOO`'#DZ'#DZQVO!rOOO%UQ&rO,59^O%aQ&rO,59gO%lQ&rO,59jO%wQ&rO,59mO&SQ&rO,59oOOOa'#D_'#D_O&_OaO'#CyO&jOaO,59]OOOb'#D`'#D`O&rObO'#C|O&}ObO,59]OOOd'#Da'#DaO'VOdO'#DPO'bOdO,59]OOO`'#Db'#DbO'jO!rO,59]O'qQ#tO'#DSOOO`,59],59]OOOp'#Dc'#DcO'vO$fO,59pOOO`,59p,59pO(OQ#|O,59rO(TQ#|O,59sOOO`-E7X-E7XO(YQ&rO'#CtOOQW'#D['#D[O(hQ&rO1G.xOOOa1G.x1G.xOOO`1G/Z1G/ZO(sQ&rO1G/ROOOb1G/R1G/RO)OQ&rO1G/UOOOd1G/U1G/UO)ZQ&rO1G/XOOO`1G/X1G/XO)fQ&rO1G/ZOOOa-E7]-E7]O)qQ#tO'#CzOOO`1G.w1G.wOOOb-E7^-E7^O)vQ#tO'#C}OOOd-E7_-E7_O){Q#tO'#DQOOO`-E7`-E7`O*QQ#|O,59nOOOp-E7a-E7aOOO`1G/[1G/[OOO`1G/^1G/^OOO`1G/_1G/_O*VQ,UO,59`OOQW-E7Y-E7YOOOa7+$d7+$dOOO`7+$u7+$uOOOb7+$m7+$mOOOd7+$p7+$pOOO`7+$s7+$sO*bQ#|O,59fO*gQ#|O,59iO*lQ#|O,59lOOO`1G/Y1G/YO*qO7[O'#CwO+SOMhO'#CwOOQW1G.z1G.zOOO`1G/Q1G/QOOO`1G/T1G/TOOO`1G/W1G/WOOOO'#D]'#D]O+eO7[O,59cOOQW,59c,59cOOOO'#D^'#D^O+vOMhO,59cOOOO-E7Z-E7ZOOQW1G.}1G.}OOOO-E7[-E7[",stateData:",c~O!_OS~OUSOVPOWQOXROYTO[]O][O^^O_^Oa^Ob^Oc^Od^Oy^O|_O!eZO~OgaO~OgbO~OgcO~OgdO~OgeO~O!XfOPmP![mP~O!YiOQpP![pP~O!ZlORsP![sP~OUSOVPOWQOXROYTOZqO[]O][O^^O_^Oa^Ob^Oc^Od^Oy^O!eZO~O![rO~P#gO!]sO!fuO~OgvO~OgwO~OS|OT}OiyO~OS!POT}OiyO~OS!ROT}OiyO~OS!TOT}OiyO~OS}OT}OiyO~O!XfOPmX![mX~OP!WO![!XO~O!YiOQpX![pX~OQ!ZO![!XO~O!ZlORsX![sX~OR!]O![!XO~O![!XO~P#gOg!_O~O!]sO!f!aO~OS!bO~OS!cO~Oj!dOShXThXihX~OS!fOT!gOiyO~OS!hOT!gOiyO~OS!iOT!gOiyO~OS!jOT!gOiyO~OS!gOT!gOiyO~Og!kO~Og!lO~Og!mO~OS!nO~Ol!qO!a!oO!c!pO~OS!rO~OS!sO~OS!tO~Ob!uOc!uOd!uO!a!wO!b!uO~Ob!xOc!xOd!xO!c!wO!d!xO~Ob!uOc!uOd!uO!a!{O!b!uO~Ob!xOc!xOd!xO!c!{O!d!xO~OT~cbd!ey|!e~",goto:"%q!aPPPPPPPPPPPPPPPPPPPPP!b!hP!nPP!zP!}#Q#T#Z#^#a#g#j#m#s#y!bP!b!bP$P$V$m$s$y%P%V%]%cPPPPPPPP%iX^OX`pXUOX`pezabcde{!O!Q!S!UR!q!dRhUR!XhXVOX`pRkVR!XkXWOX`pRnWR!XnXXOX`pQrXR!XpXYOX`pQ`ORx`Q{aQ!ObQ!QcQ!SdQ!UeZ!e{!O!Q!S!UQ!v!oR!z!vQ!y!pR!|!yQgUR!VgQjVR!YjQmWR![mQpXR!^pQtZR!`tS_O`ToXp",nodeNames:"\u26A0 StartCloseTag StartCloseTag StartCloseTag EndTag SelfClosingEndTag StartTag StartTag StartTag StartTag StartTag StartCloseTag StartCloseTag StartCloseTag IncompleteTag IncompleteCloseTag Document Text EntityReference CharacterReference InvalidEntity Element OpenTag TagName Attribute AttributeName Is AttributeValue UnquotedAttributeValue ScriptText CloseTag OpenTag StyleText CloseTag OpenTag TextareaText CloseTag OpenTag CloseTag SelfClosingTag Comment ProcessingInst MismatchedCloseTag CloseTag DoctypeDecl",maxTerm:68,context:iM,nodeProps:[["closedBy",-10,1,2,3,7,8,9,10,11,12,13,"EndTag",6,"EndTag SelfClosingEndTag",-4,22,31,34,37,"CloseTag"],["openedBy",4,"StartTag StartCloseTag",5,"StartTag",-4,30,33,36,38,"OpenTag"],["group",-10,14,15,18,19,20,21,40,41,42,43,"Entity",17,"Entity TextContent",-3,29,32,35,"TextContent Entity"],["isolate",-11,22,30,31,33,34,36,37,38,39,42,43,"ltr",-3,27,28,40,""]],propSources:[hM],skippedNodes:[0],repeatNodeCount:9,tokenData:"!<p!aR!YOX$qXY,QYZ,QZ[$q[]&X]^,Q^p$qpq,Qqr-_rs3_sv-_vw3}wxHYx}-_}!OH{!O!P-_!P!Q$q!Q![-_![!]Mz!]!^-_!^!_!$S!_!`!;x!`!a&X!a!c-_!c!}Mz!}#R-_#R#SMz#S#T1k#T#oMz#o#s-_#s$f$q$f%W-_%W%oMz%o%p-_%p&aMz&a&b-_&b1pMz1p4U-_4U4dMz4d4e-_4e$ISMz$IS$I`-_$I`$IbMz$Ib$Kh-_$Kh%#tMz%#t&/x-_&/x&EtMz&Et&FV-_&FV;'SMz;'S;:j!#|;:j;=`3X<%l?&r-_?&r?AhMz?Ah?BY$q?BY?MnMz?MnO$q!Z$|caPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr$qrs&}sv$qvw+Pwx(tx!^$q!^!_*V!_!a&X!a#S$q#S#T&X#T;'S$q;'S;=`+z<%lO$q!R&bXaP!b`!dpOr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&Xq'UVaP!dpOv&}wx'kx!^&}!^!_(V!_;'S&};'S;=`(n<%lO&}P'pTaPOv'kw!^'k!_;'S'k;'S;=`(P<%lO'kP(SP;=`<%l'kp([S!dpOv(Vx;'S(V;'S;=`(h<%lO(Vp(kP;=`<%l(Vq(qP;=`<%l&}a({WaP!b`Or(trs'ksv(tw!^(t!^!_)e!_;'S(t;'S;=`*P<%lO(t`)jT!b`Or)esv)ew;'S)e;'S;=`)y<%lO)e`)|P;=`<%l)ea*SP;=`<%l(t!Q*^V!b`!dpOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!Q*vP;=`<%l*V!R*|P;=`<%l&XW+UYlWOX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+PW+wP;=`<%l+P!Z+}P;=`<%l$q!a,]`aP!b`!dp!_^OX&XXY,QYZ,QZ]&X]^,Q^p&Xpq,Qqr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&X!_-ljiSaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx!P-_!P!Q$q!Q!^-_!^!_*V!_!a&X!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q[/ebiSlWOX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+PS0rXiSqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0mS1bP;=`<%l0m[1hP;=`<%l/^!V1vciSaP!b`!dpOq&Xqr1krs&}sv1kvw0mwx(tx!P1k!P!Q&X!Q!^1k!^!_*V!_!a&X!a#s1k#s$f&X$f;'S1k;'S;=`3R<%l?Ah1k?Ah?BY&X?BY?Mn1k?MnO&X!V3UP;=`<%l1k!_3[P;=`<%l-_!Z3hV!ahaP!dpOv&}wx'kx!^&}!^!_(V!_;'S&};'S;=`(n<%lO&}!_4WiiSlWd!ROX5uXZ7SZ[5u[^7S^p5uqr8trs7Sst>]tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^/^!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!Z5zblWOX5uXZ7SZ[5u[^7S^p5uqr5urs7Sst+Ptw5uwx7Sx!]5u!]!^7w!^!a7S!a#S5u#S#T7S#T;'S5u;'S;=`8n<%lO5u!R7VVOp7Sqs7St!]7S!]!^7l!^;'S7S;'S;=`7q<%lO7S!R7qOb!R!R7tP;=`<%l7S!Z8OYlWb!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!Z8qP;=`<%l5u!_8{iiSlWOX5uXZ7SZ[5u[^7S^p5uqr8trs7Sst/^tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^:j!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!_:sbiSlWb!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!V<QciSOp7Sqr;{rs7Sst0mtw;{wx7Sx!P;{!P!Q7S!Q!];{!]!^=]!^!a7S!a#s;{#s$f7S$f;'S;{;'S;=`>P<%l?Ah;{?Ah?BY7S?BY?Mn;{?MnO7S!V=dXiSb!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!V>SP;=`<%l;{!_>YP;=`<%l8t!_>dhiSlWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^/^!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!Z@TalWOX@OXZAYZ[@O[^AY^p@Oqr@OrsAYsw@OwxAYx!]@O!]!^Az!^!aAY!a#S@O#S#TAY#T;'S@O;'S;=`Bq<%lO@O!RA]UOpAYq!]AY!]!^Ao!^;'SAY;'S;=`At<%lOAY!RAtOc!R!RAwP;=`<%lAY!ZBRYlWc!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!ZBtP;=`<%l@O!_COhiSlWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^Dj!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!_DsbiSlWc!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!VFQbiSOpAYqrE{rsAYswE{wxAYx!PE{!P!QAY!Q!]E{!]!^GY!^!aAY!a#sE{#s$fAY$f;'SE{;'S;=`G|<%l?AhE{?Ah?BYAY?BY?MnE{?MnOAY!VGaXiSc!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!VHPP;=`<%lE{!_HVP;=`<%lBw!ZHcW!cxaP!b`Or(trs'ksv(tw!^(t!^!_)e!_;'S(t;'S;=`*P<%lO(t!aIYliSaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OKQ!O!P-_!P!Q$q!Q!^-_!^!_*V!_!a&X!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!aK_kiSaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx!P-_!P!Q$q!Q!^-_!^!_*V!_!`&X!`!aMS!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!TM_XaP!b`!dp!fQOr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&X!aNZ!ZiSgQaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OMz!O!PMz!P!Q$q!Q![Mz![!]Mz!]!^-_!^!_*V!_!a&X!a!c-_!c!}Mz!}#R-_#R#SMz#S#T1k#T#oMz#o#s-_#s$f$q$f$}-_$}%OMz%O%W-_%W%oMz%o%p-_%p&aMz&a&b-_&b1pMz1p4UMz4U4dMz4d4e-_4e$ISMz$IS$I`-_$I`$IbMz$Ib$Je-_$Je$JgMz$Jg$Kh-_$Kh%#tMz%#t&/x-_&/x&EtMz&Et&FV-_&FV;'SMz;'S;:j!#|;:j;=`3X<%l?&r-_?&r?AhMz?Ah?BY$q?BY?MnMz?MnO$q!a!$PP;=`<%lMz!R!$ZY!b`!dpOq*Vqr!$yrs(Vsv*Vwx)ex!a*V!a!b!4t!b;'S*V;'S;=`*s<%lO*V!R!%Q]!b`!dpOr*Vrs(Vsv*Vwx)ex}*V}!O!%y!O!f*V!f!g!']!g#W*V#W#X!0`#X;'S*V;'S;=`*s<%lO*V!R!&QX!b`!dpOr*Vrs(Vsv*Vwx)ex}*V}!O!&m!O;'S*V;'S;=`*s<%lO*V!R!&vV!b`!dp!ePOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!'dX!b`!dpOr*Vrs(Vsv*Vwx)ex!q*V!q!r!(P!r;'S*V;'S;=`*s<%lO*V!R!(WX!b`!dpOr*Vrs(Vsv*Vwx)ex!e*V!e!f!(s!f;'S*V;'S;=`*s<%lO*V!R!(zX!b`!dpOr*Vrs(Vsv*Vwx)ex!v*V!v!w!)g!w;'S*V;'S;=`*s<%lO*V!R!)nX!b`!dpOr*Vrs(Vsv*Vwx)ex!{*V!{!|!*Z!|;'S*V;'S;=`*s<%lO*V!R!*bX!b`!dpOr*Vrs(Vsv*Vwx)ex!r*V!r!s!*}!s;'S*V;'S;=`*s<%lO*V!R!+UX!b`!dpOr*Vrs(Vsv*Vwx)ex!g*V!g!h!+q!h;'S*V;'S;=`*s<%lO*V!R!+xY!b`!dpOr!+qrs!,hsv!+qvw!-Swx!.[x!`!+q!`!a!/j!a;'S!+q;'S;=`!0Y<%lO!+qq!,mV!dpOv!,hvx!-Sx!`!,h!`!a!-q!a;'S!,h;'S;=`!.U<%lO!,hP!-VTO!`!-S!`!a!-f!a;'S!-S;'S;=`!-k<%lO!-SP!-kO|PP!-nP;=`<%l!-Sq!-xS!dp|POv(Vx;'S(V;'S;=`(h<%lO(Vq!.XP;=`<%l!,ha!.aX!b`Or!.[rs!-Ssv!.[vw!-Sw!`!.[!`!a!.|!a;'S!.[;'S;=`!/d<%lO!.[a!/TT!b`|POr)esv)ew;'S)e;'S;=`)y<%lO)ea!/gP;=`<%l!.[!R!/sV!b`!dp|POr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!0]P;=`<%l!+q!R!0gX!b`!dpOr*Vrs(Vsv*Vwx)ex#c*V#c#d!1S#d;'S*V;'S;=`*s<%lO*V!R!1ZX!b`!dpOr*Vrs(Vsv*Vwx)ex#V*V#V#W!1v#W;'S*V;'S;=`*s<%lO*V!R!1}X!b`!dpOr*Vrs(Vsv*Vwx)ex#h*V#h#i!2j#i;'S*V;'S;=`*s<%lO*V!R!2qX!b`!dpOr*Vrs(Vsv*Vwx)ex#m*V#m#n!3^#n;'S*V;'S;=`*s<%lO*V!R!3eX!b`!dpOr*Vrs(Vsv*Vwx)ex#d*V#d#e!4Q#e;'S*V;'S;=`*s<%lO*V!R!4XX!b`!dpOr*Vrs(Vsv*Vwx)ex#X*V#X#Y!+q#Y;'S*V;'S;=`*s<%lO*V!R!4{Y!b`!dpOr!4trs!5ksv!4tvw!6Vwx!8]x!a!4t!a!b!:]!b;'S!4t;'S;=`!;r<%lO!4tq!5pV!dpOv!5kvx!6Vx!a!5k!a!b!7W!b;'S!5k;'S;=`!8V<%lO!5kP!6YTO!a!6V!a!b!6i!b;'S!6V;'S;=`!7Q<%lO!6VP!6lTO!`!6V!`!a!6{!a;'S!6V;'S;=`!7Q<%lO!6VP!7QOyPP!7TP;=`<%l!6Vq!7]V!dpOv!5kvx!6Vx!`!5k!`!a!7r!a;'S!5k;'S;=`!8V<%lO!5kq!7yS!dpyPOv(Vx;'S(V;'S;=`(h<%lO(Vq!8YP;=`<%l!5ka!8bX!b`Or!8]rs!6Vsv!8]vw!6Vw!a!8]!a!b!8}!b;'S!8];'S;=`!:V<%lO!8]a!9SX!b`Or!8]rs!6Vsv!8]vw!6Vw!`!8]!`!a!9o!a;'S!8];'S;=`!:V<%lO!8]a!9vT!b`yPOr)esv)ew;'S)e;'S;=`)y<%lO)ea!:YP;=`<%l!8]!R!:dY!b`!dpOr!4trs!5ksv!4tvw!6Vwx!8]x!`!4t!`!a!;S!a;'S!4t;'S;=`!;r<%lO!4t!R!;]V!b`!dpyPOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!;uP;=`<%l!4t!V!<TXjSaP!b`!dpOr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&X",tokenizers:[lM,aM,cM,oM,nM,rM,0,1,2,3,4,5],topRules:{Document:[0,16]},dialects:{noMatch:0,selfClosing:515},tokenPrec:517});function kx(i,e){let t=Object.create(null);for(let n of i.getChildren(yx)){let r=n.getChild(UA),s=n.getChild(Fp)||n.getChild(Sx);r&&(t[e.read(r.from,r.to)]=s?s.type.id==Fp?e.read(s.from+1,s.to-1):e.read(s.from,s.to):"")}return t}function dx(i,e){let t=i.getChild(IA);return t?e.read(t.from,t.to):" "}function Gp(i,e,t){let n;for(let r of t)if(!r.attrs||r.attrs(n||(n=kx(i.node.parent.firstChild,e))))return{parser:r.parser,bracketed:!0};return null}function tO(i=[],e=[]){let t=[],n=[],r=[],s=[];for(let l of i)(l.tag=="script"?t:l.tag=="style"?n:l.tag=="textarea"?r:s).push(l);let o=e.length?Object.create(null):null;for(let l of e)(o[l.name]||(o[l.name]=[])).push(l);return hc((l,a)=>{let c=l.type.id;if(c==BA)return Gp(l,a,t);if(c==VA)return Gp(l,a,n);if(c==YA)return Gp(l,a,r);if(c==bx&&s.length){let h=l.node,f=h.firstChild,u=f&&dx(f,a),d;if(u){for(let p of s)if(p.tag==u&&(!p.attrs||p.attrs(d||(d=kx(f,a))))){let O=h.lastChild,g=O.type.id==WA?O.from:h.to;if(g>f.to)return{parser:p.parser,overlay:[{from:f.to,to:g}]}}}}if(o&&c==yx){let h=l.node,f;if(f=h.firstChild){let u=o[a.read(f.from,f.to)];if(u)for(let d of u){if(d.tagName&&d.tagName!=dx(h.parent,a))continue;let p=h.lastChild;if(p.type.id==Fp){let O=p.from+1,g=p.lastChild,b=p.to-(g&&g.isError?0:1);if(b>O)return{parser:d.parser,overlay:[{from:O,to:b}],bracketed:!0}}else if(p.type.id==Sx)return{parser:d.parser,overlay:[{from:p.from,to:p.to}]}}}}return null})}var fM=148,Qx=1,uM=149,dM=150,Px=2,pM=151,OM=3,gM=4,mM=152,Cx=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288],bM=58,yM=40,Tx=95,SM=91,nh=45,xM=46,wM=35,kM=37,QM=38,vM=92,$M=10,PM=42;function Ko(i){return i>=65&&i<=90||i>=97&&i<=122||i>=161}function iO(i){return i>=48&&i<=57}function vx(i){return iO(i)||i>=97&&i<=102||i>=65&&i<=70}var Ax=(i,e,t)=>(n,r)=>{for(let s=!1,o=0,l=0;;l++){let{next:a}=n;if(Ko(a)||a==nh||a==Tx||s&&iO(a))!s&&(a!=nh||l>0)&&(s=!0),o===l&&a==nh&&o++,n.advance();else if(a==vM&&n.peek(1)!=$M){if(n.advance(),vx(n.next)){do n.advance();while(vx(n.next));n.next==32&&n.advance()}else n.next>-1&&n.advance();s=!0}else{s&&n.acceptToken(o>=2&&r.canShift(Px)?e:a==yM?t:i);break}}},CM=new Me(Ax(uM,Px,dM),{contextual:!0}),TM=new Me(Ax(pM,OM,gM),{contextual:!0}),AM=new Me(i=>{if(Cx.includes(i.peek(-1))){let{next:e}=i;(Ko(e)||e==Tx||e==wM||e==xM||e==PM||e==SM||e==bM&&Ko(i.peek(1))||e==nh||e==QM)&&i.acceptToken(fM)}}),MM=new Me(i=>{if(!Cx.includes(i.peek(-1))){let{next:e}=i;if(e==kM&&(i.advance(),i.acceptToken(Qx)),Ko(e)){do i.advance();while(Ko(i.next)||iO(i.next));i.acceptToken(Qx)}}});function $x(i){return/^#[a-f\d]{3}([a-f\d]{3}([a-f\d]{2})?)?$/i.test(i)?mM:-1}var RM=Bt({"AtKeyword import charset namespace keyframes media supports font-feature-values":m.definitionKeyword,"from to selector scope MatchFlag":m.keyword,NamespaceName:m.namespace,KeyframeName:m.labelName,KeyframeRangeName:m.operatorKeyword,TagName:m.tagName,ClassName:m.className,PseudoClassName:m.constant(m.className),IdName:m.labelName,"FeatureName PropertyName":m.propertyName,AttributeName:m.attributeName,NumberLiteral:m.number,KeywordQuery:m.keyword,UnaryQueryOp:m.operatorKeyword,"CallTag ValueName FontName":m.atom,VariableName:m.variableName,Callee:m.operatorKeyword,Unit:m.unit,"UniversalSelector NestingSelector":m.definitionOperator,"MatchOp CompareOp":m.compareOperator,"ChildOp SiblingOp, LogicOp":m.logicOperator,BinOp:m.arithmeticOperator,Important:m.modifier,Comment:m.blockComment,ColorLiteral:m.color,"ParenthesizedContent StringLiteral":m.string,":":m.punctuation,PseudoOp:m.derefOperator,"; , |":m.separator,"( )":m.paren,"[ ]":m.squareBracket,"{ }":m.brace}),XM={__proto__:null,lang:44,"nth-child":44,"nth-last-child":44,"nth-of-type":44,"nth-last-of-type":44,dir:44,"host-context":44,if:88,url:158,"url-prefix":158,domain:158,regexp:158},EM={__proto__:null,or:102,and:102,not:112,only:112,layer:212},ZM={__proto__:null,selector:118,style:124,layer:208},_M={__proto__:null,"@import":204,"@media":216,"@charset":220,"@namespace":224,"@keyframes":230,"@supports":242,"@scope":246,"@font-feature-values":252},LM={__proto__:null,to:249},Mx=an.deserialize({version:14,states:"MrQYQdOOO$TQdOOP$[O`OOO%XQaO'#CfOOQP'#Ce'#CeO%`QdO'#CgO%eQ`O'#CgO%jQaO'#FrO&eQdO'#CkO'XQaO'#CcO'cQdO'#CnOOQP'#ES'#ESOOQP'#ER'#ERO'nQdO'#ETO'yQdO'#E[O'yQdO'#E_OOQP'#Fr'#FrO)`QhO'#FQOOQS'#Fq'#FqOOQS'#FT'#FTQYQdOOO)gQdO'#EeO*vQhO'#EkO)gQdO'#EmO*}QdO'#EoO+YQdO'#ErO*[QhO'#ExO+bQdO'#EzO+mQdO'#E}O+rQaO'#CfO+yQ`O'#EbO,OQ`O'#GPO,ZQdO'#GPQOQ`OOP,eO&jO'#CaPOOO)CAa)CAaOOQP'#Ci'#CiOOQP,59R,59RO%`QdO,59ROOQP'#Cm'#CmOOQP,59V,59VO&eQdO,59VO,pQdO,59YOOQP,5:m,5:mO'nQdO,5:oO'yQdO,5:vO'yQdO,5:xO'yQdO,5:yO'yQdO'#F[O,{Q`O,58}O-TQdO'#EaOOQS,58},58}OOQP'#Cq'#CqOOQO'#EP'#EPOOQP,59Y,59YO-[Q`O,59YO-aQ`O,59YO-fQpO'#EUO-qQdO'#EVO-vQ`O'#EVO-{QpO,5:oO.iQaO,5:vO/PQaO,5:yOOQW'#D]'#D]O0OQhO'#DgO0cQhO,5;lO*[QhO'#DeO0pQ`O'#DnO0uQhO'#D{OOQW'#Fx'#FxOOQS,5;l,5;lO0zQ`O'#DhO1PQ`O'#DkOOQS-E9R-E9ROOQ['#Cv'#CvO1UQdO'#CwO1iQdO'#C|O1|QdO'#DPOOQ['#DQ'#DQO2aQ!pO'#DRO4jQ!jO,5;POOQO'#DW'#DWO-aQ`O'#DVO4zQ!nO'#FuO6}Q`O'#DXO7SQ`O'#D|OOQ['#Fu'#FuO7XQhO'#GSO7gQ`O,5;VO7lQ!bO,5;XOOQS'#Eq'#EqO7tQ`O,5;ZO7yQdO,5;ZOOQO'#Et'#EtO8RQ`O,5;^O8WQhO,5;dO'yQdO'#DjOOQS,5;f,5;fO0zQ`O,5;fO8`QdO,5;fOOQS'#Fc'#FcO8hQdO'#FPO7gQ`O,5;iO8pQdO,5:|O9QQdO'#F^O9_Q`O,5<kO9_Q`O,5<kPOOO'#FS'#FSP9jO&jO,58{POOO,58{,58{OOQP1G.m1G.mOOQP1G.q1G.qOOQP1G.t1G.tO-[Q`O1G.tO-aQ`O1G.tO9uQpO1G0ZO9}QaO1G0bO:eQaO1G0dO:{QaO1G0eO;cQaO,5;vOOQO-E9Y-E9YOOQS1G.i1G.iO;mQ`O,5:{O;rQdO'#EQO;yQdO'#CuOOQO'#EX'#EXOOQO,5:q,5:qO-qQdO,5:qOOQP1G0Z1G0ZO)gQdO1G0ZO<QQ!jO'#D]O<`Q!bO,59xO<hQhO,5:ROOQO'#Dc'#DcOOQO'#Fy'#FyO<cQ!bO,59|O<pQhO'#FdO*[QhO,59zO*[QhO'#FdO=hQhO1G1WOOQS1G1W1G1WO=rQhO,5:PO>mQhO'#DoOOQW,5:Y,5:YOOQW,5:g,5:gOOQW,5:S,5:SO>wQhO,5:VO?cQ!fO'#FvOOQS'#Fv'#FvOOQS'#FV'#FVO@pQdO,59cOOQ[,59c,59cOATQdO,59hOOQ[,59h,59hOAhQdO,59kOOQ[,59k,59kOOQ[,59m,59mO)gQdO,59oOA{QhO'#EgOOQW'#Eg'#EgOBjQ`O1G0kO4sQhO1G0kOOQ[,59q,59qO*[QhO'#DZOOQ[,59s,59sOBoQ#tO,5:hOBzQhO'#F`OCXQ`O,5<nOOQS1G0q1G0qOOQS1G0s1G0sOOQS1G0u1G0uOCdQ`O1G0uOCiQdO'#EuOOQS1G0x1G0xOOQS1G1O1G1OOCtQaO,5:UO7gQ`O1G1QOOQS1G1Q1G1QO0zQ`O1G1QOOQS-E9a-E9aOOQS1G1T1G1TOC{Q!fO1G0hODcQ`O'#EdOOQO1G0h1G0hOOQO,5;x,5;xODhQdO,5;xOOQO-E9[-E9[ODuQ`O1G2VPOOO-E9Q-E9QPOOO1G.g1G.gOOQP7+$`7+$`OOQP7+%u7+%uO)gQdO7+%uOOQS1G0g1G0gOEQQaO'#F}OE[Q`O,5:lOEaQ!fO'#FUOF_QdO'#FtOFiQ`O,59aOOQO1G0]1G0]OFnQ!bO7+%uO)gQdO1G/dOFyQhO1G/hOOQW1G/m1G/mOOQW1G/f1G/fOG[QhO,5<OOOQW-E9b-E9bOOQS7+&r7+&rOHSQhO'#D]OHbQhO'#F|OHmQ`O'#F|OHrQ`O,5:ZOHwQ!bO'#D_O>wQhO'#DmOISQhO'#DsOI[QhO'#DuOIaQ!jO'#F{OOQO'#F{'#F{OIlQ`O'#DxOItQ!bO'#DzOOQO'#Fz'#FzOIyQ`O1G/qOOQS-E9T-E9TOOQ[1G.}1G.}OOQ[1G/S1G/SOOQ[1G/V1G/VOOQ[1G/Z1G/ZOJOQdO,5;ROOQS7+&V7+&VOJTQ`O7+&VOJYQhO'#D[OJbQ`O,59uO*[QhO,59uOOQ[1G0S1G0SOJjQ`O1G0SOJoQhO,5;zOOQO-E9^-E9^OOQS7+&a7+&aOJ}QbO'#DROOQO'#Ew'#EwOK]Q`O'#EvOOQO'#Ev'#EvOKhQ`O'#FaOKpQdO,5;aOOQS,5;a,5;aOOQ[1G/p1G/pOOQS7+&l7+&lO7gQ`O7+&lOK{Q!fO'#F]O)gQdO'#F]OMSQdO7+&SOOQO7+&S7+&SOOQO,5;O,5;OOOQO1G1d1G1dOMgQ!bO<<IaOMrQdO'#FZOM|Q`O,5<iOOQP1G0W1G0WOOQS-E9S-E9SONUQdO'#FYON`Q`O,5<`OOQ]1G.{1G.{OOQP<<Ia<<IaONhQ`O<<IaONmQdO7+%OOOQO'#D_'#D_ONtQ!bO7+%SON|QhO'#FXO! ZQ`O,5<hO)gQdO,5<hOOQW1G/u1G/uO! cQ`O,5:XO>wQhO'#DtOOQO,5:_,5:_O! hQhO,5:aO! pQhO,5:fO)gQdO,5:dOOQW7+%]7+%]OOQO'#Ei'#EiO! wQ`O1G0mOOQS<<Iq<<IqO)gQdO,59vO!!kQhO1G/aOOQ[1G/a1G/aO!!rQ`O1G/aOOQW-E9U-E9UOOQ[7+%n7+%nOOQO,5;b,5;bOClQdO'#FbOKhQ`O,5;{OOQS,5;{,5;{OOQS-E9_-E9_OOQS1G0{1G0{OOQS<<JW<<JWO!!zQ!fO,5;wOOQS-E9Z-E9ZOOQO<<In<<InOOQPAN>{AN>{O!$RQ`OAN>{O!$WQaO,5;uOOQO-E9X-E9XO!$bQdO,5;tOOQO-E9W-E9WOOQW<<Hj<<HjOOQW<<Hn<<HnO!$lQhO<<HnO!$}QhO'#D]O!%]QhO,5;sO!%hQ`O,5;sOOQO-E9V-E9VO!%mQdO1G2SO!%wQhO1G/sO!&PQ`O,5:`O>wQhO'#DwOOQO1G/{1G/{O!&UQ!bO1G0QO!&^QdO1G0OOJOQdO'#F_O!&eQ`O7+&XOOQW7+&X7+&XO!&mQ!bO1G/bOOQ[7+${7+${O!&xQhO7+${P!'PQ`O'#FWOOQO,5;|,5;|OOQO-E9`-E9`OOQS1G1g1G1gOOQPG24gG24gO!'UQ`OAN>YO)gQdO1G1_O!'ZQ`O7+'nOOQO1G/z1G/zO!'cQ`O,5:cO!'hQhO7+%lOOQO,5;y,5;yOOQO-E9]-E9]OOQW<<Is<<IsOOQ[<<Hg<<HgPOQW,5;r,5;rOOQWG23tG23tO!'oQdO7+&yOOQO1G/}1G/}OOQO<<IW<<IW",stateData:"!(S~O$`OS$aQQ~OWVO^`O`WOcYOdYOlaOo]O#P^O#S_O#YeO#`fO#bgO#dhO#giO#mjO#okO#rlO$ZRO$^ZO$gTO$rZO~OQnOWVO^`O`WOcYOdYOlaOo]O#P^O#S_O#YeO#`fO#bgO#dhO#giO#mjO#okO#rlO$ZmO$^ZO$gTO$rZO~O$X$sP~P!mO$arO~O`YXcYXdYXoYXrYX!eYX#PYX#SYX$YYX$^YX$g[X$rYX~OgYX~P$aO$ZtO~O$gvO~O$gvO`$fXc$fXd$fXo$fXr$fX!e$fX#P$fX#S$fX$Y$fX$^$fX$r$fXg$fX~O$ZwO~O`yOczOdzOo|O#P}O#S!PO$Y!OO$^ZO$rZO~Or!SO!e!QO~P&jOf!YO$Z!UO$[!VO~OW!]O$Z!ZO$g![O~OWVO^`O`WOcYOdYOo]O#P^O#S_O$ZRO$^ZO$gTO$rZO~OS!eOc!fOd!fOh!bOr!SO!Y!dO!]!iO!`!jO$]!aO~Om!hO~P(qOQ!uOh!mOo!nOr!oOv!xO|!vO!q!wO$Z!lO$[!sO$^!pO$k!qO~OS!eOc!fOd!fOh!bO!Y!dO!]!iO!`!jO$]!aO~Or$vP~P*[Ov!}O!q!wO$Z!|O~Ov#PO$Z#PO~Oh#SOr!SO#p#UO~O$Z#WO~Oc#VX~P$aOc#ZO~Om#[O$X$sXq$sX~O$X$sXq$sX~P!mO$b#_O$c#_O$d#aO~Of#fO$Z!UO$[!VO~Or!SO!e!QO~Oq$sP~P!mOh#oO~Oh#pO~On!xX!|!xX$g!zX~O$Z#qO~O$g#sO~On#tO!|#uO~O`yOczOdzOo|O$^ZO$rZO~Or#Oa!e#Oa#P#Oa#S#Oa$Y#Oag#Oa~P.TOr#Ra!e#Ra#P#Ra#S#Ra$Y#Rag#Ra~P.TOS!eOc!fOd!fOh!bO!Y!dO!]!iO!`!jO~OR#zOv#zO$]#vO$^#yO$k!qO~P/gOm$QO!T#}O!e$OO~P(qOh$SO~O$]$UO~Oh#SO~Oh$WO~O`$YOc$YOg$]Ol$YOm$YO~P)gO`$YOc$YOl$YOm$YOn$_O~P)gO`$YOc$YOl$YOm$YOq$aO~P)gOP$bOSuXcuXduXhuXmuXxuX!YuX!]uX!`uX#[uX#^uX$]uX!WuXQuX`uXguXluXouXruXvuX|uX!quX$ZuX$[uX$^uX$kuXnuXquX!euX$XuX$uuX!}uX~Ox$cO#[$dO#^$eOm$vP~P*[Oh#pOS$iXc$iXd$iXm$iXx$iX!Y$iX!]$iX!`$iX#[$iX#^$iX$]$iXQ$iX`$iXg$iXl$iXo$iXr$iXv$iX|$iX!q$iX$Z$iX$[$iX$^$iX$k$iXn$iXq$iX!e$iX$X$iX$u$iX!}$iX~Oh$iO~Oh$kO~O!T#}O!e$lOr$vXm$vX~Or!SO~Om$oOx$cO~Om$pO~Ov$qO!q!wO~Or$rO~Or!SO!T#}O~Or!SO#p$xO~O$Z#WOr#sX~O$u$|Om#Ua$X#Uaq#Ua~P)gOm$QX$X$QXq$QX~P!mOm#[O$X$saq$sa~O$b#_O$c#_O$d%TO~On%VO!|%WO~Or#Oi!e#Oi#P#Oi#S#Oi$Y#Oig#Oi~P.TOr#Qi!e#Qi#P#Qi#S#Qi$Y#Qig#Qi~P.TOr#Ri!e#Ri#P#Ri#S#Ri$Y#Rig#Ri~P.TOr$Oa!e$Oa~P&jOq%XO~Og$qP~P'yOg$hP~P)gOc!RXg!PX!T!PX!W!RX~Oc%aO!W%bO~Og%cO!T#}O~O!T#}OS$WXc$WXd$WXh$WXm$WXr$WX!Y$WX!]$WX!`$WX!e$WX$]$WX~Om%gO!e$OO~P(qO!T#}OS!Xac!Xad!Xah!Xam!Xar!Xa!Y!Xa!]!Xa!`!Xa!e!Xa$]!Xag!Xa~O$]%hOg$pP~P/gOR#zOS!eOh%mOv#zO!Y%nO$]%lO$^#yO$k!qO~Ox$cOQ$jX`$jXc$jXg$jXh$jXl$jXm$jXo$jXr$jXv$jX|$jX!q$jX$Z$jX$[$jX$^$jX$k$jXn$jXq$jX~O`$YOc$YOg%wOl$YOm$YO~P)gO`$YOc$YOl$YOm$YOn%xO~P)gO`$YOc$YOl$YOm$YOq%yO~P)gOh%{OS#ZXc#ZXd#ZXm#ZX!Y#ZX!]#ZX!`#ZX$]#ZX~Om%|O~Og&ROv&SO!r&SO~Or$SX!e$SXm$SX~P*[O!e$lOr$vam$va~Om&VO~Oq&^O$Z&XO$k&WO~Og&_O~P&jOx$cO!e&cO$u$|Om#Ui$X#Uiq#Ui~P)gO$t&fO~Om$Qa$X$Qaq$Qa~P!mOm#[O$X$siq$si~O!e&iOg$qX~P&jOg&kO~Ox$cOQ#xXg#xXh#xXo#xXr#xXv#xX|#xX!e#xX!q#xX$Z#xX$[#xX$^#xX$k#xX~O!e&mOg$hX~P)gOg&oO~On&pOx$cO!}&qO~OR#zOv#zO$]&sO$^#yO$k!qO~O!T#}OS$Wac$Wad$Wah$Wam$War$Wa!Y$Wa!]$Wa!`$Wa!e$Wa$]$Wa~Oc!dXg!PX!T!PX!e!PX~O!T#}O!e&uOg$pX~Oc&wO~Og&xO~Oc!mXg!mX!W!RX~OS!eOh&zO~O!T&|O~O!T&|O!W&}Og$oX~Oc'OOg!lX~O!W&}O~Og'PO~O$Z'QO~Om'SO~Oc'TO!T#}O~Og'VOm'UO~Og'YO~O!T#}Or$Sa!e$Sam$Sa~OP$bOruX!euXguX~O$k&WOr#jX!e#jX~Or!SO!e'[O~Oq'`O$Z&XO$k&WO~Ox$cOQ$PXh$PXm$PXo$PXr$PXv$PX|$PX!e$PX!q$PX$X$PX$Z$PX$[$PX$^$PX$k$PX$u$PXq$PX~O!e&cO$u$|Om#Uq$X#Uqq#Uq~P)gOn'eOx$cO!}'fO~Og#}X!e#}X~P'yO!e&iOg$qa~Og#|X!e#|X~P)gO!e&mOg$ha~On'eO~Og'kO~P)gOg'lO!W'mO~O$]'nOg#{X!e#{X~P/gO!e&uOg$pa~Og'sO~OS!eOh'uO~OS!eO~PFyO`'yOg'{O~OS#zac#zad#zah#za!Y#za!]#za!`#za$]#za~Og'}O~P!!POg'}Om(OO~Ox$cOQ$Pah$Pam$Pao$Par$Pav$Pa|$Pa!e$Pa!q$Pa$X$Pa$Z$Pa$[$Pa$^$Pa$k$Pa$u$Paq$Pa~On(TO~Og#}a!e#}a~P&jOg#|a!e#|a~P)gOR#zOv#zO$]&sO$^#yO$k&WO~Oc!fXg!PX!T!PX!e!PX~O!T#}Og#{a!e#{a~Oc(VO~O!e&uOg$pi~P)gOg!ai!T!ji~Og(XO~O!W(ZOg!ni~Og!li~P)gO`'yOg(^O~Ox$cOg!Oim!Oi~Og(_O~P!!POm(`O~Og(aO~O!e&uOg$pq~Og(cO~OS!eO~P!$lOg#{q!e#{q~P)gO$`!r$a$k`$kx#S~",goto:"8^$wPPPPP$xP${P%U%h%U%z&^P%UP&d%UPP&jPPP&p&z&zPPPP&zPP&z&z'jP&zP&z(m&zP)])`)f)f)x)fP)f*_P)fP)f)fP*j)fP*v*|+r+uP+x*v+{*v,O,U,X,_,X)f,ePP-Z-a%U-g%U.V.V.].aPP%UP%U%UP.g/c/p/w${P0QP0TP${P${P${P0Z${P0^0a0d0k${P${PP${P0p${P0s0y1Y1t2S2Y2d2j2p2v2|3W3^3d3j3p3vPPPPPPPPPPPP3|4VP4{5O6SP6[7U7k,X7w7zP7}PP8TRsQ_bOPdp!S#[%Pq`OP^_dp}!O!P!Q!S#S#[#o%P&iqSOP^_dp}!O!P!Q!S#S#[#o%P&iqUOP^_dp}!O!P!Q!S#S#[#o%P&iQuTR#bvQxWR#cyQ!WYR#dzQ#d!YS$h!t!uR%U#f!Z!xeg!m!n!o#Z#p#u$[$^$`$c${%W%]%a&c&d&m&r&w'O'T'i'r'x(V(b!Y!xeg!m!n!o#Z#p#u$[$^$`$c${%W%]%a&c&d&m&r&w'O'T'i'r'x(V(bb#z!b$W%b%m&z&}'m'u(ZU&Z$r&]'[R'Z&Y!Z!teg!m!n!o#Z#p#u$[$^$`$c${%W%]%a&c&d&m&r&w'O'T'i'r'x(V(bR$j!vQ&P$iR'W&Qq!gafj!b!c!d!r#}$O$P$S$g$i$l&Q&uQ#w!bW%s$W%m&z'uQ&t%bQ'w&}Q(U'mR(d(Zc#z!b$W%b%m&z&}'m'u(ZQ#VkQ$V!iQ$v#UR&a$xX%q$W%m&z'up!gafj!b!c!d!r#}$O$P$S$g$i$l&Q&uW%p$W%m&z'uQ&{%nQ'v&|Q'w&}R(d(ZR$T!eR%j$SR'p&uR&{%nX%o$W%m&z'uR'v&|X%t$W%m&z'uX%r$W%m&z'u!Y!xeg!m!n!o#Z#p#u$[$^$`$c${%W%]%a&c&d&m&r&w'O'T'i'r'x(V(bQ!}hR$q#OQ!XYR#ezQ#d!XR%U#ep[OP^_dp}!O!P!Q!S#S#[#o%P&ie{X!_!`#h#i#j#k$u%Y'gQ!^]R#g|T!]]|Q#r![R%_#sQ!TXQ!haQ#TkQ#m!RQ$Q!cQ$n!zQ$t#RQ$w#VQ$z#YQ%g$PQ&`$vQ'^&[Q'a&aR(S']SoP!SQ#^pQ%O#[R&g%PZnPp!S#[%PQ$}#ZQ&e${R'd&dR$g!rQ'R%{R(['yR#OhR#QiR$s#QS&[$r&]R(Q'[V&Y$r&]'[R#YlQ#`rR%S#`QdOSpP!SU!kdp%PR%P#[Q%]#p[&l%]&r'i'r'x(bQ&r%aQ'i&mQ'r&wQ'x'OR(b(VQ$[!mQ$^!nQ$`!oV%v$[$^$`Q&Q$iR'X&QQ&v%iS'q&v(WR(W'rQ&n%]R'j&nQ&j%YR'h&jQ!RXR#l!RQ&d${R'c&dQ#]oS%Q#]%RR%R#^Q'z'RR(]'zQ$m!yR&U$mQ&]$rR'_&]Q']&[R(R']Q#XlR$y#XQ$P!cR%f$P_cOPdp!S#[%P^XOPdp!S#[%PQ!_^Q!`_Q#h}Q#i!OQ#j!PQ#k!QQ$u#SQ%Y#oR'g&iR%^#pQ!reQ!{g[$X!m!n!o$[$^$`Q${#Zh%[#p%]%a&m&r&w'O'i'r'x(V(bQ%`#uQ%z$cS&b${&dQ&h%WQ'b&cR'|'T]$Z!m!n!o$[$^$`Q!caU!yf!r$gQ#RjQ#x!bS#|!c$PQ$R!dQ%d#}Q%e$OQ%i$SS&O$i&QQ&T$lR'o&uQ#{!bW%s$W%m&z'uQ&t%bQ'w&}Q(U'mR(d(ZQ%u$WQ&y%mQ't&zR(Y'uR%k$SR%Z#oQqPR#n!SQ!zfQ$f!rR%}$g",nodeNames:"\u26A0 Unit VariableName VariableName QueryCallee Comment StyleSheet RuleSet UniversalSelector TagSelector TagName NamespacedTagSelector NamespaceName TagName NestingSelector ClassSelector . ClassName PseudoClassSelector : :: PseudoClassName PseudoClassName ) ( ArgList ValueName ParenthesizedValue AtKeyword ; ] [ BracketedValue } { BracedValue ColorLiteral NumberLiteral StringLiteral BinaryExpression BinOp CallExpression Callee IfExpression if ArgList IfBranch KeywordQuery FeatureQuery FeatureName BinaryQuery LogicOp ComparisonQuery ColorLiteral CompareOp UnaryQuery UnaryQueryOp ParenthesizedQuery SelectorQuery selector ParenthesizedSelector StyleQuery style ParenthesedQuery CallQuery ArgList PropertyName , PropertyName UnaryQuery ParenthesedQuery BinaryQuery ParenthesedQuery ParenthesedQuery StyleFeature PropertyName StyleRange PseudoQuery CallLiteral CallTag ParenthesizedContent PseudoClassName ArgList IdSelector IdName AttributeSelector AttributeName NamespacedAttribute NamespaceName AttributeName MatchOp MatchFlag ChildSelector ChildOp DescendantSelector SiblingSelector SiblingOp Block Declaration PropertyName Important ImportStatement import Layer layer LayerName layer MediaStatement media CharsetStatement charset NamespaceStatement namespace NamespaceName KeyframesStatement keyframes KeyframeName KeyframeList KeyframeSelector KeyframeRangeName SupportsStatement supports ScopeStatement scope to FontFeatureStatement font-feature-values FontName AtRule Styles",maxTerm:176,nodeProps:[["isolate",-2,5,38,""],["openedBy",23,"(",30,"[",33,"{"],["closedBy",24,")",31,"]",34,"}"]],propSources:[RM],skippedNodes:[0,5,130],repeatNodeCount:17,tokenData:"IO~R!bOX%ZX^&R^p%Zpq&Rqr)ers)vst+jtu/wuv%Zvw0qwx1Sxy2qyz3Sz{3X{|3r|}8e}!O8v!O!P9e!P!Q9|!Q![:u![!];p!]!^<l!^!_<}!_!`=y!`!a>^!a!b%Z!b!c?_!c!k%Z!k!lAl!l!u%Z!u!vAl!v!}%Z!}#OA}#O#P%Z#P#QB`#Q#R/w#R#]%Z#]#^Bq#^#g%Z#g#hAl#h#o%Z#o#pGU#p#qGg#q#rHO#r#sHa#s#y%Z#y#z&R#z$f%Z$f$g&R$g#BY%Z#BY#BZ&R#BZ$IS%Z$IS$I_&R$I_$I|%Z$I|$JO&R$JO$JT%Z$JT$JU&R$JU$KV%Z$KV$KW&R$KW&FU%Z&FU&FV&R&FV;'S%Z;'S;=`Hx<%lO%Z`%^SOy%jz;'S%j;'S;=`%{<%lO%j`%oS!r`Oy%jz;'S%j;'S;=`%{<%lO%j`&OP;=`<%l%j~&Wh$`~OX%jX^'r^p%jpq'rqy%jz#y%j#y#z'r#z$f%j$f$g'r$g#BY%j#BY#BZ'r#BZ$IS%j$IS$I_'r$I_$I|%j$I|$JO'r$JO$JT%j$JT$JU'r$JU$KV%j$KV$KW'r$KW&FU%j&FU&FV'r&FV;'S%j;'S;=`%{<%lO%j~'yh$`~!r`OX%jX^'r^p%jpq'rqy%jz#y%j#y#z'r#z$f%j$f$g'r$g#BY%j#BY#BZ'r#BZ$IS%j$IS$I_'r$I_$I|%j$I|$JO'r$JO$JT%j$JT$JU'r$JU$KV%j$KV$KW'r$KW&FU%j&FU&FV'r&FV;'S%j;'S;=`%{<%lO%jj)jS$uYOy%jz;'S%j;'S;=`%{<%lO%j~)yWOY)vZr)vrs*cs#O)v#O#P*h#P;'S)v;'S;=`+d<%lO)v~*hOv~~*kRO;'S)v;'S;=`*t;=`O)v~*wXOY)vZr)vrs*cs#O)v#O#P*h#P;'S)v;'S;=`+d;=`<%l)v<%lO)v~+gP;=`<%l)vj+maOy%jz}%j}!O,r!O!Q%j!Q![,r![!c%j!c!},r!}#O%j#O#P.O#P#R%j#R#S,r#S#T%j#T#o,r#o$g%j$g;'S,r;'S;=`/q<%lO,rj,ya$rY!r`Oy%jz}%j}!O,r!O!Q%j!Q![,r![!c%j!c!},r!}#O%j#O#P.O#P#R%j#R#S,r#S#T%j#T#o,r#o$g%j$g;'S,r;'S;=`/q<%lO,rj.TV!r`OY,rYZ%jZy,ryz.jz;'S,r;'S;=`/q<%lO,rY.oX$rY}!O.j!Q![.j!c!}.j#O#P/[#R#S.j#T#o.j$g;'S.j;'S;=`/k<%lO.jY/_SOY.jZ;'S.j;'S;=`/k<%lO.jY/nP;=`<%l.jj/tP;=`<%l,rd/zUOy%jz!_%j!_!`0^!`;'S%j;'S;=`%{<%lO%jd0eS!|S!r`Oy%jz;'S%j;'S;=`%{<%lO%jb0vS^QOy%jz;'S%j;'S;=`%{<%lO%j~1VWOY1SZw1Swx*cx#O1S#O#P1o#P;'S1S;'S;=`2k<%lO1S~1rRO;'S1S;'S;=`1{;=`O1S~2OXOY1SZw1Swx*cx#O1S#O#P1o#P;'S1S;'S;=`2k;=`<%l1S<%lO1S~2nP;=`<%l1Sj2vShYOy%jz;'S%j;'S;=`%{<%lO%j~3XOg~n3`UWQxWOy%jz!_%j!_!`0^!`;'S%j;'S;=`%{<%lO%jj3yWxW#SQOy%jz!O%j!O!P4c!P!Q%j!Q![7h![;'S%j;'S;=`%{<%lO%jj4hU!r`Oy%jz!Q%j!Q![4z![;'S%j;'S;=`%{<%lO%jj5RY!r`$kYOy%jz!Q%j!Q![4z![!g%j!g!h5q!h#X%j#X#Y5q#Y;'S%j;'S;=`%{<%lO%jj5vY!r`Oy%jz{%j{|6f|}%j}!O6f!O!Q%j!Q![6}![;'S%j;'S;=`%{<%lO%jj6kU!r`Oy%jz!Q%j!Q![6}![;'S%j;'S;=`%{<%lO%jj7UU!r`$kYOy%jz!Q%j!Q![6}![;'S%j;'S;=`%{<%lO%jj7o[!r`$kYOy%jz!O%j!O!P4z!P!Q%j!Q![7h![!g%j!g!h5q!h#X%j#X#Y5q#Y;'S%j;'S;=`%{<%lO%jj8jS!eYOy%jz;'S%j;'S;=`%{<%lO%jj8{WxWOy%jz!O%j!O!P4c!P!Q%j!Q![7h![;'S%j;'S;=`%{<%lO%jj9jU`YOy%jz!Q%j!Q![4z![;'S%j;'S;=`%{<%lO%j~:RTxWOy%jz{:b{;'S%j;'S;=`%{<%lO%j~:iS!r`$a~Oy%jz;'S%j;'S;=`%{<%lO%jj:z[$kYOy%jz!O%j!O!P4z!P!Q%j!Q![7h![!g%j!g!h5q!h#X%j#X#Y5q#Y;'S%j;'S;=`%{<%lO%jj;uUcYOy%jz![%j![!]<X!];'S%j;'S;=`%{<%lO%jj<`SdY!r`Oy%jz;'S%j;'S;=`%{<%lO%jj<qSmYOy%jz;'S%j;'S;=`%{<%lO%jh=SU!WWOy%jz!_%j!_!`=f!`;'S%j;'S;=`%{<%lO%jh=mS!WW!r`Oy%jz;'S%j;'S;=`%{<%lO%jl>QS!WW!|SOy%jz;'S%j;'S;=`%{<%lO%jj>eV#PQ!WWOy%jz!_%j!_!`=f!`!a>z!a;'S%j;'S;=`%{<%lO%jb?RS#PQ!r`Oy%jz;'S%j;'S;=`%{<%lO%jj?bYOy%jz}%j}!O@Q!O!c%j!c!}@o!}#T%j#T#o@o#o;'S%j;'S;=`%{<%lO%jj@VW!r`Oy%jz!c%j!c!}@o!}#T%j#T#o@o#o;'S%j;'S;=`%{<%lO%jj@v[lY!r`Oy%jz}%j}!O@o!O!Q%j!Q![@o![!c%j!c!}@o!}#T%j#T#o@o#o;'S%j;'S;=`%{<%lO%jhAqS!}WOy%jz;'S%j;'S;=`%{<%lO%jjBSSoYOy%jz;'S%j;'S;=`%{<%lO%jnBeSn^Oy%jz;'S%j;'S;=`%{<%lO%jjBvU!}WOy%jz#a%j#a#bCY#b;'S%j;'S;=`%{<%lO%jbC_U!r`Oy%jz#d%j#d#eCq#e;'S%j;'S;=`%{<%lO%jbCvU!r`Oy%jz#c%j#c#dDY#d;'S%j;'S;=`%{<%lO%jbD_U!r`Oy%jz#f%j#f#gDq#g;'S%j;'S;=`%{<%lO%jbDvU!r`Oy%jz#h%j#h#iEY#i;'S%j;'S;=`%{<%lO%jbE_U!r`Oy%jz#T%j#T#UEq#U;'S%j;'S;=`%{<%lO%jbEvU!r`Oy%jz#b%j#b#cFY#c;'S%j;'S;=`%{<%lO%jbF_U!r`Oy%jz#h%j#h#iFq#i;'S%j;'S;=`%{<%lO%jbFxS$tQ!r`Oy%jz;'S%j;'S;=`%{<%lO%jjGZSrYOy%jz;'S%j;'S;=`%{<%lO%jfGlU$gUOy%jz!_%j!_!`0^!`;'S%j;'S;=`%{<%lO%jjHTSqYOy%jz;'S%j;'S;=`%{<%lO%jfHfU#SQOy%jz!_%j!_!`0^!`;'S%j;'S;=`%{<%lO%j`H{P;=`<%l%Z",tokenizers:[AM,MM,CM,TM,1,2,3,4,new ln("m~RRYZ[z{a~~g~aO$c~~dP!P!Qg~lO$d~~",28,156)],topRules:{StyleSheet:[0,6],Styles:[1,129]},dynamicPrecedences:{97:1},specialized:[{term:172,get:(i,e)=>$x(i)<<1,external:$x},{term:150,get:i=>XM[i]||-1},{term:151,get:i=>EM[i]||-1},{term:4,get:i=>ZM[i]||-1},{term:28,get:i=>_M[i]||-1},{term:149,get:i=>LM[i]||-1}],tokenPrec:2433});var nO=null;function rO(){if(!nO&&typeof document=="object"&&document.body){let{style:i}=document.body,e=[],t=new Set;for(let n in i)n!="cssText"&&n!="cssFloat"&&typeof i[n]=="string"&&(/[A-Z]/.test(n)&&(n=n.replace(/[A-Z]/g,r=>"-"+r.toLowerCase())),t.has(n)||(e.push(n),t.add(n)));nO=e.sort().map(n=>({type:"property",label:n,apply:n+": "}))}return nO||[]}var Rx=["active","after","any-link","autofill","backdrop","before","checked","cue","default","defined","disabled","empty","enabled","file-selector-button","first","first-child","first-letter","first-line","first-of-type","focus","focus-visible","focus-within","fullscreen","has","host","host-context","hover","in-range","indeterminate","invalid","is","lang","last-child","last-of-type","left","link","marker","modal","not","nth-child","nth-last-child","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","part","placeholder","placeholder-shown","read-only","read-write","required","right","root","scope","selection","slotted","target","target-text","valid","visited","where"].map(i=>({type:"class",label:i})),Xx=["above","absolute","activeborder","additive","activecaption","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","antialiased","appworkspace","asterisks","attr","auto","auto-flow","avoid","avoid-column","avoid-page","avoid-region","axis-pan","background","backwards","baseline","below","bidi-override","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","clear","clip","close-quote","col-resize","collapse","color","color-burn","color-dodge","column","column-reverse","compact","condensed","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","darken","dashed","decimal","decimal-leading-zero","default","default-button","dense","destination-atop","destination-in","destination-out","destination-over","difference","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic-abegede-gez","ethiopic-halehame-aa-er","ethiopic-halehame-gez","ew-resize","exclusion","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fill-box","fixed","flat","flex","flex-end","flex-start","footnotes","forwards","from","geometricPrecision","graytext","grid","groove","hand","hard-light","help","hidden","hide","higher","highlight","highlighttext","horizontal","hsl","hsla","hue","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-grid","inline-table","inset","inside","intrinsic","invert","italic","justify","keep-all","landscape","large","larger","left","level","lighter","lighten","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-hexadecimal","lower-latin","lower-norwegian","lowercase","ltr","luminosity","manipulation","match","matrix","matrix3d","medium","menu","menutext","message-box","middle","min-intrinsic","mix","monospace","move","multiple","multiple_mask_images","multiply","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","opacity","open-quote","optimizeLegibility","optimizeSpeed","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","perspective","pinch-zoom","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row","row-resize","row-reverse","rtl","run-in","running","s-resize","sans-serif","saturation","scale","scale3d","scaleX","scaleY","scaleZ","screen","scroll","scrollbar","scroll-position","se-resize","self-start","self-end","semi-condensed","semi-expanded","separate","serif","show","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","soft-light","solid","source-atop","source-in","source-out","source-over","space","space-around","space-between","space-evenly","spell-out","square","start","static","status-bar","stretch","stroke","stroke-box","sub","subpixel-antialiased","svg_masks","super","sw-resize","symbolic","symbols","system-ui","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","text","text-bottom","text-top","textarea","textfield","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","to","top","transform","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","unidirectional-pan","unset","up","upper-latin","uppercase","url","var","vertical","vertical-text","view-box","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","wrap","wrap-reverse","x-large","x-small","xor","xx-large","xx-small"].map(i=>({type:"keyword",label:i})).concat(["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"].map(i=>({type:"constant",label:i}))),DM=["a","abbr","address","article","aside","b","bdi","bdo","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","dd","del","details","dfn","dialog","div","dl","dt","em","figcaption","figure","footer","form","header","hgroup","h1","h2","h3","h4","h5","h6","hr","html","i","iframe","img","input","ins","kbd","label","legend","li","main","meter","nav","ol","output","p","pre","ruby","section","select","small","source","span","strong","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","tr","u","ul"].map(i=>({type:"type",label:i})),jM=["@charset","@color-profile","@container","@counter-style","@font-face","@font-feature-values","@font-palette-values","@import","@keyframes","@layer","@media","@namespace","@page","@position-try","@property","@scope","@starting-style","@supports","@view-transition"].map(i=>({type:"keyword",label:i})),Ti=/^(\w[\w-]*|-\w[\w-]*|)$/,IM=/^-(-[\w-]*)?$/;function UM(i,e){var t;if((i.name=="("||i.type.isError)&&(i=i.parent||i),i.name!="ArgList")return!1;let n=(t=i.parent)===null||t===void 0?void 0:t.firstChild;return n?.name!="Callee"?!1:e.sliceString(n.from,n.to)=="var"}var Ex=new rs,BM=["Declaration"];function VM(i){for(let e=i;;){if(e.type.isTop)return e;if(!(e=e.parent))return i}}function Zx(i,e,t){if(e.to-e.from>4096){let n=Ex.get(e);if(n)return n;let r=[],s=new Set,o=e.cursor(F.IncludeAnonymous);if(o.firstChild())do for(let l of Zx(i,o.node,t))s.has(l.label)||(s.add(l.label),r.push(l));while(o.nextSibling());return Ex.set(e,r),r}else{let n=[],r=new Set;return e.cursor().iterate(s=>{var o;if(t(s)&&s.matchContext(BM)&&((o=s.node.nextSibling)===null||o===void 0?void 0:o.name)==":"){let l=i.sliceString(s.from,s.to);r.has(l)||(r.add(l),n.push({label:l,type:"variable"}))}}),n}}var YM=i=>e=>{let{state:t,pos:n}=e,r=ee(t).resolveInner(n,-1),s=r.type.isError&&r.from==r.to-1&&t.doc.sliceString(r.from,r.to)=="-";if(r.name=="PropertyName"||(s||r.name=="TagName")&&/^(Block|Styles)$/.test(r.resolve(r.to).name))return{from:r.from,options:rO(),validFor:Ti};if(r.name=="ValueName")return{from:r.from,options:Xx,validFor:Ti};if(r.name=="PseudoClassName")return{from:r.from,options:Rx,validFor:Ti};if(i(r)||(e.explicit||s)&&UM(r,t.doc))return{from:i(r)||s?r.from:n,options:Zx(t.doc,VM(r),i),validFor:IM};if(r.name=="TagName"){for(let{parent:a}=r;a;a=a.parent)if(a.name=="Block")return{from:r.from,options:rO(),validFor:Ti};return{from:r.from,options:DM,validFor:Ti}}if(r.name=="AtKeyword")return{from:r.from,options:jM,validFor:Ti};if(!e.explicit)return null;let o=r.resolve(n),l=o.childBefore(n);return l&&l.name==":"&&o.name=="PseudoClassSelector"?{from:n,options:Rx,validFor:Ti}:l&&l.name==":"&&o.name=="Declaration"||o.name=="ArgList"?{from:n,options:Xx,validFor:Ti}:o.name=="Block"||o.name=="Styles"?{from:n,options:rO(),validFor:Ti}:null},zM=YM(i=>i.name=="VariableName"),Jo=Hi.define({name:"css",parser:Mx.configure({props:[$i.add({Declaration:os()}),fi.add({"Block KeyframeList":Sc})]}),languageData:{commentTokens:{block:{open:"/*",close:"*/"}},indentOnInput:/^\s*\}$/,wordChars:"-"}});function _x(){return new Yt(Jo,Jo.data.of({autocomplete:zM}))}var WM=317,NM=318,Lx=1,qM=2,GM=3,FM=4,HM=319,KM=321,JM=322,e5=5,t5=6,i5=0,oO=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288],Dx=125,n5=59,lO=47,r5=42,s5=43,o5=45,l5=60,a5=44,c5=63,h5=46,f5=91,u5=new Os({start:!1,shift(i,e){return e==e5||e==t5||e==KM?i:e==JM},strict:!1}),d5=new Me((i,e)=>{let{next:t}=i;(t==Dx||t==-1||e.context)&&i.acceptToken(HM)},{contextual:!0,fallback:!0}),p5=new Me((i,e)=>{let{next:t}=i,n;oO.indexOf(t)>-1||t==lO&&((n=i.peek(1))==lO||n==r5)||t!=Dx&&t!=n5&&t!=-1&&!e.context&&i.acceptToken(WM)},{contextual:!0}),O5=new Me((i,e)=>{i.next==f5&&!e.context&&i.acceptToken(NM)},{contextual:!0}),g5=new Me((i,e)=>{let{next:t}=i;if(t==s5||t==o5){if(i.advance(),t==i.next){i.advance();let n=!e.context&&e.canShift(Lx);i.acceptToken(n?Lx:qM)}}else t==c5&&i.peek(1)==h5&&(i.advance(),i.advance(),(i.next<48||i.next>57)&&i.acceptToken(GM))},{contextual:!0});function sO(i,e){return i>=65&&i<=90||i>=97&&i<=122||i==95||i>=192||!e&&i>=48&&i<=57}var m5=new Me((i,e)=>{if(i.next!=l5||!e.dialectEnabled(i5)||(i.advance(),i.next==lO))return;let t=0;for(;oO.indexOf(i.next)>-1;)i.advance(),t++;if(sO(i.next,!0)){for(i.advance(),t++;sO(i.next,!1);)i.advance(),t++;for(;oO.indexOf(i.next)>-1;)i.advance(),t++;if(i.next==a5)return;for(let n=0;;n++){if(n==7){if(!sO(i.next,!0))return;break}if(i.next!="extends".charCodeAt(n))break;i.advance(),t++}}i.acceptToken(FM,-t)}),b5=Bt({"get set async static":m.modifier,"for while do if else switch try catch finally return throw break continue default case defer":m.controlKeyword,"in of await yield void typeof delete instanceof as satisfies":m.operatorKeyword,"let var const using function class extends":m.definitionKeyword,"import export from":m.moduleKeyword,"with debugger new":m.keyword,TemplateString:m.special(m.string),super:m.atom,BooleanLiteral:m.bool,this:m.self,null:m.null,Star:m.modifier,VariableName:m.variableName,"CallExpression/VariableName TaggedTemplateExpression/VariableName":m.function(m.variableName),VariableDefinition:m.definition(m.variableName),Label:m.labelName,PropertyName:m.propertyName,PrivatePropertyName:m.special(m.propertyName),"CallExpression/MemberExpression/PropertyName":m.function(m.propertyName),"FunctionDeclaration/VariableDefinition":m.function(m.definition(m.variableName)),"ClassDeclaration/VariableDefinition":m.definition(m.className),"NewExpression/VariableName":m.className,PropertyDefinition:m.definition(m.propertyName),PrivatePropertyDefinition:m.definition(m.special(m.propertyName)),UpdateOp:m.updateOperator,"LineComment Hashbang":m.lineComment,BlockComment:m.blockComment,Number:m.number,String:m.string,Escape:m.escape,ArithOp:m.arithmeticOperator,LogicOp:m.logicOperator,BitOp:m.bitwiseOperator,CompareOp:m.compareOperator,RegExp:m.regexp,Equals:m.definitionOperator,Arrow:m.function(m.punctuation),": Spread":m.punctuation,"( )":m.paren,"[ ]":m.squareBracket,"{ }":m.brace,"InterpolationStart InterpolationEnd":m.special(m.brace),".":m.derefOperator,", ;":m.separator,"@":m.meta,TypeName:m.typeName,TypeDefinition:m.definition(m.typeName),"type enum interface implements namespace module declare":m.definitionKeyword,"abstract global Privacy readonly override":m.modifier,"is keyof unique infer asserts":m.operatorKeyword,JSXAttributeValue:m.attributeValue,JSXText:m.content,"JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag":m.angleBracket,"JSXIdentifier JSXNameSpacedName":m.tagName,"JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName":m.attributeName,"JSXBuiltin/JSXIdentifier":m.standard(m.tagName)}),y5={__proto__:null,export:20,as:25,from:33,default:36,async:41,function:42,in:52,out:55,const:56,extends:60,this:64,true:72,false:72,null:84,void:88,typeof:92,super:108,new:142,delete:154,yield:163,await:167,class:172,public:237,private:237,protected:237,readonly:239,instanceof:258,satisfies:261,import:294,keyof:351,unique:355,infer:361,asserts:397,is:399,abstract:419,implements:421,type:423,let:426,var:428,using:431,interface:437,enum:441,namespace:447,module:449,declare:453,global:457,defer:473,for:478,of:487,while:490,with:494,do:498,if:502,else:504,switch:508,case:514,try:520,catch:524,finally:528,return:532,throw:536,break:540,continue:544,debugger:548},S5={__proto__:null,async:129,get:131,set:133,declare:195,public:197,private:197,protected:197,static:199,abstract:201,override:203,readonly:209,accessor:211,new:403},x5={__proto__:null,"<":193},jx=an.deserialize({version:14,states:"$FvQ%TQlOOO%[QlOOO'_QpOOP(lO`OOO*zQ!0MxO'#CiO+RO#tO'#CjO+aO&jO'#CjO+oO#@ItO'#DaO.QQlO'#DgO.bQlO'#DrO%[QlO'#DzO0fQlO'#ESOOQ!0Lf'#E['#E[O1PQ`O'#EXOOQO'#Ep'#EpOOQO'#Im'#ImO1XQ`O'#GtO1dQ`O'#EoO1iQ`O'#EoO3hQ!0MxO'#JsO6[Q!0MxO'#JtO6uQ`O'#F^O6zQ,UO'#FuOOQ!0Lf'#Fg'#FgO7VO7dO'#FgO9XQMhO'#F}O9`Q`O'#F|OOQ!0Lf'#Jt'#JtOOQ!0Lb'#Js'#JsO9eQ`O'#GxOOQ['#K`'#K`O9pQ`O'#IZO9uQ!0LrO'#I[OOQ['#Ja'#JaOOQ['#I`'#I`Q`QlOOQ`QlOOO9}Q!L^O'#DvO:UQlO'#EOO:]QlO'#EQO9kQ`O'#GtO:dQMhO'#CoO:rQ`O'#EnO:}Q`O'#EzO;hQMhO'#FfO;xQ`O'#GtOOQO'#Ka'#KaO;}Q`O'#KaO<]Q`O'#G|O<]Q`O'#G}O<]Q`O'#HPO9kQ`O'#HSO=SQ`O'#HVO>kQ`O'#CeO>{Q`O'#HdO?TQ`O'#HjO?TQ`O'#HlO`QlO'#HnO?TQ`O'#HpO?TQ`O'#HsO?YQ`O'#HyO?_Q!0LsO'#IPO%[QlO'#IRO?jQ!0LsO'#ITO?uQ!0LsO'#IVO9uQ!0LrO'#IXO@QQ!0MxO'#CiOASQpO'#DlQOQ`OOO%[QlO'#EQOAjQ`O'#ETO:dQMhO'#EnOAuQ`O'#EnOBQQ!bO'#FfOOQ['#Cg'#CgOOQ!0Lb'#Dq'#DqOOQ!0Lb'#Jw'#JwO%[QlO'#JwOOQO'#Jz'#JzOOQO'#Ii'#IiOCQQpO'#EgOOQ!0Lb'#Ef'#EfOOQ!0Lb'#KO'#KOOC|Q!0MSO'#EgODWQpO'#EWOOQO'#Jy'#JyODlQpO'#JzOEyQpO'#EWODWQpO'#EgPFWO&2DjO'#CbPOOO)CEO)CEOOOOO'#Ia'#IaOFcO#tO,59UOOQ!0Lh,59U,59UOOOO'#Ib'#IbOFqO&jO,59UOGPQ!L^O'#DcOOOO'#Id'#IdOGWO#@ItO,59{OOQ!0Lf,59{,59{OGfQlO'#IeOGyQ`O'#JuOIxQ!fO'#JuO+}QlO'#JuOJPQ`O,5:ROJgQ`O'#EpOJtQ`O'#KUOKPQ`O'#KTOKPQ`O'#KTOKXQ`O,5;^OK^Q`O'#KSOOQ!0Ln,5:^,5:^OKeQlO,5:^OMcQ!0MxO,5:fONSQ`O,5:nONmQ!0LrO'#KRONtQ`O'#KQO9eQ`O'#KQO! YQ`O'#KQO! bQ`O,5;]O! gQ`O'#KQO!#lQ!fO'#JtOOQ!0Lh'#Ci'#CiO%[QlO'#ESO!$[Q!fO,5:sOOQS'#J{'#J{OOQO-E<k-E<kO9kQ`O,5=`O!$rQ`O,5=`O!$wQlO,5;ZO!&zQMhO'#EkO!(eQ`O,5;ZO!(jQlO'#DyO!(tQpO,5;eO!(|QpO,5;eO%[QlO,5;eOOQ['#FU'#FUOOQ['#FW'#FWO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fO%[QlO,5;fOOQ['#F['#F[O!)[QlO,5;uOOQ!0Lf,5;z,5;zOOQ!0Lf,5;{,5;{OOQ!0Lf,5;},5;}O%[QlO'#IqO!+_Q!0LrO,5<jO%[QlO,5;fO!&zQMhO,5;fO!+|QMhO,5;fO!-nQMhO'#E^O%[QlO,5;xOOQ!0Lf,5;|,5;|O!-uQ,UO'#FkO!.rQ,UO'#KYO!.^Q,UO'#KYO!.yQ,UO'#KYOOQO'#KY'#KYO!/_Q,UO,5<TOOOW,5<a,5<aO!/pQlO'#FwOOOW'#Ip'#IpO7VO7dO,5<RO!/wQ,UO'#FyOOQ!0Lf,5<R,5<RO!0hQ$IUO'#CyOOQ!0Lh'#C}'#C}O!0{O#@ItO'#DRO!1iQMjO,5<fO!1pQ`O,5<iO!3YQ(CWO'#GYO!3jQ`O'#GZO!3oQ`O'#GZO!5_Q(CWO'#G_O!6dQpO'#GcOOQO'#Go'#GoO!,TQMhO'#GnOOQO'#Gq'#GqO!,TQMhO'#GpO!7VQ$IUO'#JmOOQ!0Lh'#Jm'#JmO!7aQ`O'#JlO!7oQ`O'#JkO!7wQ`O'#CuOOQ!0Lh'#C{'#C{O!8YQ`O'#C}OOQ!0Lh'#DV'#DVOOQ!0Lh'#DX'#DXO!8_Q`O,5<fO1SQ`O'#DZO!,TQMhO'#GQO!,TQMhO'#GSO!8gQ`O'#GUO!8lQ`O'#GVO!3oQ`O'#G]O!,TQMhO'#GbO<]Q`O'#JlO!8qQ`O'#EqO!9`Q`O,5<hOOQ!0Lb'#Cr'#CrO!9hQ`O'#ErO!:bQpO'#EsOOQ!0Lb'#KS'#KSO!:iQ!0LrO'#KbO9uQ!0LrO,5=dO`QlO,5>uOOQ['#Ji'#JiOOQ[,5>v,5>vOOQ[-E<^-E<^O!<hQ!0MxO,5:bO!=[QpO,5:`O!?WQ!0MxO,5:jO%[QlO,5:jO!AnQ!0MxO,5:lOOQO,5@{,5@{O!B_QMhO,5=`O!BmQ!0LrO'#JjO9`Q`O'#JjO!COQ!0LrO,59ZO!CZQpO,59ZO!CcQMhO,59ZO:dQMhO,59ZO!CnQ`O,5;ZO!CvQ`O'#HcO!D[Q`O'#KeO%[QlO,5<OO!=[QpO,5<QO!DdQ`O,5={O!DiQ`O,5={O!DnQ`O,5={O!D|Q`O,5={O9uQ!0LrO,5={O<]Q`O,5=kOOQO'#Cy'#CyO!ETQpO,5=hO!E]QMhO,5=iO!EhQ`O,5=kO!EmQ!bO,5=nO!EuQ`O'#KaO?YQ`O'#HXO9kQ`O'#HZO!EzQ`O'#HZO:dQMhO'#H]O!FPQ`O'#H]OOQ[,5=q,5=qO!FUQ`O'#H^O!FgQ`O'#CoO!FlQ`O,59PO!FvQ`O,59PO!H{QlO,59POOQ[,59P,59PO!I]Q!0LrO,59PO%[QlO,59PO!KhQlO'#HfOOQ['#Hg'#HgOOQ['#Hh'#HhO`QlO,5>OO!LOQ`O,5>OO`QlO,5>UO`QlO,5>WO!LTQ`O,5>YO`QlO,5>[O!LYQ`O,5>_O!L_QlO,5>eOOQ[,5>k,5>kO%[QlO,5>kO9uQ!0LrO,5>mOOQ[,5>o,5>oO#!iQ`O,5>oOOQ[,5>q,5>qO#!iQ`O,5>qOOQ[,5>s,5>sO##SQpO'#D_O%[QlO'#JwO##^QpO'#JwO##xQpO'#DmO#$ZQpO'#DmO#&lQlO'#DmO#&sQ`O'#JvO#&{Q`O,5:WOOQO'#Et'#EtO#'QQ`O'#EtO#'VQ`O'#KVO#'_Q`O,5;_O#'dQpO'#DmO#'qQpO'#EVOOQ!0Lf,5:o,5:oO%[QlO,5:oO#'xQ`O,5:oO?YQ`O,5;YO!CZQpO,5;YO!CcQMhO,5;YO:dQMhO,5;YO#(QQ`O,5@cO#(VQ07dO,5:sOOQO-E<g-E<gO#)]Q!0MSO,5;RODWQpO,5:rO#)gQpO,5:rODWQpO,5;RO!COQ!0LrO,5:rOOQ!0Lb'#Ej'#EjOOQO,5;R,5;RO%[QlO,5;RO#)tQ!0LrO,5;RO#*PQ!0LrO,5;RO!CZQpO,5:rOOQO,5;X,5;XO#*_Q!0LrO,5;RPOOO'#I_'#I_P#*sO&2DjO,58|POOO,58|,58|OOOO-E<_-E<_OOQ!0Lh1G.p1G.pOOOO-E<`-E<`OOOO,59},59}O#+OQ!bO,59}OOOO-E<b-E<bOOQ!0Lf1G/g1G/gO#+TQ!fO,5?PO+}QlO,5?POOQO,5?V,5?VO#+_QlO'#IeOOQO-E<c-E<cO#+lQ`O,5@aO#+tQ!fO,5@aO#+{Q`O,5@oOOQ!0Lf1G/m1G/mO%[QlO,5@pO#,TQ`O'#IkOOQO-E<i-E<iO#+{Q`O,5@oOOQ!0Lb1G0x1G0xOOQ!0Ln1G/x1G/xOOQ!0Ln1G0Y1G0YO%[QlO,5@mO#,iQ!0LrO,5@mO#,zQ!0LrO,5@mO#-RQ`O,5@lO9eQ`O,5@lO#-ZQ`O,5@lO#-iQ`O'#InO#-RQ`O,5@lOOQ!0Lb1G0w1G0wO!(tQpO,5:uO!)PQpO,5:uOOQS,5:w,5:wO#.ZQdO,5:wO#.cQMhO1G2zO9kQ`O1G2zOOQ!0Lf1G0u1G0uO#.qQ!0MxO1G0uO#/vQ!0MvO,5;VOOQ!0Lh'#GX'#GXO#0dQ!0MzO'#JmO!$wQlO1G0uO#2oQ!fO'#JxO%[QlO'#JxO#2yQ`O,5:eOOQ!0Lh'#D_'#D_OOQ!0Lf1G1P1G1PO%[QlO1G1POOQ!0Lf1G1g1G1gO#3OQ`O1G1PO#5dQ!0MxO1G1QO#5kQ!0MxO1G1QO#8RQ!0MxO1G1QO#8YQ!0MxO1G1QO#:pQ!0MxO1G1QO#=WQ!0MxO1G1QO#=_Q!0MxO1G1QO#=fQ!0MxO1G1QO#?|Q!0MxO1G1QO#@TQ!0MxO1G1QO#BbQ?MtO'#CiO#D]Q?MtO1G1aO#DdQ?MtO'#JtO#DwQ!0MxO,5?]OOQ!0Lb-E<o-E<oO#GUQ!0MxO1G1QO#HRQ!0MzO1G1QOOQ!0Lf1G1Q1G1QO#IUQMjO'#J}O#I`Q`O,5:xO#IeQ!0MxO1G1dO#JXQ,UO,5<XO#JaQ,UO,5<YO#JiQ,UO'#FpO#KQQ`O'#FoOOQO'#KZ'#KZOOQO'#Io'#IoO#KVQ,UO1G1oOOQ!0Lf1G1o1G1oOOOW1G1z1G1zO#KhQ?MtO'#JsO#KrQ`O,5<cO!)[QlO,5<cOOOW-E<n-E<nOOQ!0Lf1G1m1G1mO#KwQpO'#KYOOQ!0Lf,5<e,5<eO#LPQpO,5<eO#LUQMhO'#DTOOOO'#Ic'#IcO#L]O#@ItO,59mOOQ!0Lh,59m,59mO%[QlO1G2QO!8lQ`O'#IsO#LhQ`O,5<{OOQ!0Lh,5<x,5<xO!,TQMhO'#IvO#MUQMjO,5=YO!,TQMhO'#IxO#MwQMjO,5=[O!&zQMhO,5=^OOQO1G2T1G2TO#NRQ!dO'#CrO#NfQ(CWO'#ErO$ nQpO'#GcO$!UQ!dO,5<tO$!]Q`O'#K]O9eQ`O'#K]O$!kQ`O,5<vO$#RQ!dO'#C{O!,TQMhO,5<uO$#]Q`O'#G[O$#qQ`O,5<uO$#vQ!dO'#GXO$$TQ!dO'#K^O$$_Q`O'#K^O!&zQMhO'#K^O$$dQ`O,5<yO$$iQlO'#JwO$$sQpO'#GdO#$ZQpO'#GdO$%UQ`O'#GhO!3oQ`O'#GlO$%ZQ!0LrO'#IuO$%fQpO,5<}OOQ!0Lp,5<},5<}O$%mQpO'#GdO$%zQpO'#GeO$&]QpO'#GeO$&bQMjO,5=YO$&rQMjO,5=[OOQ!0Lh,5=_,5=_O!,TQMhO,5@WO!,TQMhO,5@WO$'SQ`O'#IzO$'hQ`O,5@VO$'pQ`O,59aOOQ!0Lh,59i,59iO$'uQ`O,5@WO$(uQ$IYO,59uOOQ!0Lh'#Jq'#JqO$)hQMjO,5<lO$*ZQMjO,5<nO@zQ`O,5<pOOQ!0Lh,5<q,5<qO$*eQ`O,5<wO$*jQMjO,5<|O$*zQ`O'#KQO!$wQlO1G2SO$+PQ`O1G2SO9eQ`O'#KTO$+UQ`O'#D_O9eQ`O'#EtO%[QlO'#EtO9eQ`O'#I|O$+aQ!0LrO,5@|OOQ[1G3O1G3OOOQ[1G4a1G4aOOQ!0Lf1G/|1G/|OOQ!0Lf1G/z1G/zO$-cQ!0MxO1G0UOOQ[1G2z1G2zO!&zQMhO1G2zO%[QlO1G2zO#.fQ`O1G2zO$/gQMhO'#EkOOQ!0Lb,5@U,5@UO$/tQ!0LrO,5@UOOQ[1G.u1G.uO!COQ!0LrO1G.uO!CZQpO1G.uO!CcQMhO1G.uO$0VQ`O1G0uO$0[Q`O'#CiO$0gQ`O'#KfO$0oQ`O,5=}O$0tQ`O'#KfO$0yQ`O'#KfO$1XQ`O'#JSO$1gQ`O,5APO$1oQ!fO1G1jOOQ!0Lf1G1l1G1lO9kQ`O1G3gO@zQ`O1G3gO$1vQ`O1G3gO$1{Q`O1G3gO!DnQ`O1G3gO9uQ!0LrO1G3gOOQ[1G3g1G3gO!EhQ`O1G3VO!&zQMhO1G3SO$2QQ`O1G3SOOQ[1G3T1G3TO!&zQMhO1G3TO$2VQ`O1G3TO$2_QpO'#HROOQ[1G3V1G3VO!6_QpO'#JOO!EmQ!bO1G3YOOQ[1G3Y1G3YOOQ[,5=s,5=sO$2gQMhO,5=uO9kQ`O,5=uO$%UQ`O,5=wO9`Q`O,5=wO!CZQpO,5=wO!CcQMhO,5=wO:dQMhO,5=wO$2uQ`O'#KdO$3QQ`O,5=xOOQ[1G.k1G.kO$3VQ!0LrO1G.kO@zQ`O1G.kO$3bQ`O1G.kO9uQ!0LrO1G.kO$5jQ!fO,5ARO$5wQ`O,5ARO9eQ`O,5ARO$6SQlO,5>QO$6ZQ`O,5>QOOQ[1G3j1G3jO`QlO1G3jOOQ[1G3p1G3pOOQ[1G3r1G3rO?TQ`O1G3tO$6`QlO1G3vO$:dQlO'#HuOOQ[1G3y1G3yO$:qQ`O'#H{O?YQ`O'#H}OOQ[1G4P1G4PO$:yQlO1G4PO9uQ!0LrO1G4VOOQ[1G4X1G4XOOQ!0Lb'#G`'#G`O9uQ!0LrO1G4ZO9uQ!0LrO1G4]O$?QQ`O,5@cO9eQ`O,5;`O?YQ`O,5:XO!)[QlO,5:XO!CZQpO,5:XO$?VQ?MtO,5:XOOQO,5;`,5;`O$?aQpO'#IfO$?wQ`O,5@bOOQ!0Lf1G/r1G/rO$@PQpO'#IlO$@ZQ`O,5@qOOQ!0Lb1G0y1G0yO#$ZQpO,5:XOOQO'#Ih'#IhO$@cQpO,5:qOOQ!0Ln,5:q,5:qO#'{Q`O1G0ZOOQ!0Lf1G0Z1G0ZO%[QlO1G0ZOOQ!0Lf1G0t1G0tO?YQ`O1G0tO!CZQpO1G0tO!CcQMhO1G0tOOQ!0Lb1G5}1G5}O!COQ!0LrO1G0^OOQO1G0m1G0mO%[QlO1G0mO$@jQ!0LrO1G0mO$@uQ!0LrO1G0mO!CZQpO1G0^ODWQpO1G0^O$ATQ!0LrO1G0mOOQO1G0^1G0^O$AiQ!0MxO1G0mPOOO-E<]-E<]POOO1G.h1G.hOOOO1G/i1G/iO$AsQ!bO,5<jO$A{Q!fO1G4kOOQO1G4q1G4qO%[QlO,5?PO$BVQ`O1G5{O$B_Q`O1G6ZO$BgQ!fO1G6[O9eQ`O,5?VO$BqQ!0MxO1G6XO%[QlO1G6XO$CRQ!0LrO1G6XO$CdQ`O1G6WO$CdQ`O1G6WO9eQ`O1G6WO$ClQ`O,5?YO9eQ`O,5?YOOQO,5?Y,5?YO$DQQ`O,5?YO$*zQ`O,5?YOOQO-E<l-E<lOOQS1G0a1G0aOOQS1G0c1G0cO#.^Q`O1G0cOOQ[7+(f7+(fO!&zQMhO7+(fO%[QlO7+(fO$D`Q`O7+(fO$DkQMhO7+(fO$DyQ!0MzO,5=YO$GUQ!0MzO,5=[O$IaQ!0MzO,5=YO$KrQ!0MzO,5=[O$NTQ!0MzO,59uO%!YQ!0MzO,5<lO%$eQ!0MzO,5<nO%&pQ!0MzO,5<|OOQ!0Lf7+&a7+&aO%)RQ!0MxO7+&aO%)uQlO'#IgO%*SQ`O,5@dO%*[Q!fO,5@dOOQ!0Lf1G0P1G0PO%*fQ`O7+&kOOQ!0Lf7+&k7+&kO%*kQ?MtO,5:fO%[QlO7+&{O%*uQ?MtO,5:bO%+SQ?MtO,5:jO%+^Q?MtO,5:lO%+hQMhO'#IjO%+rQ`O,5@iOOQ!0Lh1G0d1G0dOOQO1G1s1G1sOOQO1G1t1G1tO%+zQ!jO,5<[O!)[QlO,5<ZOOQO-E<m-E<mOOQ!0Lf7+'Z7+'ZOOOW7+'f7+'fOOOW1G1}1G1}O%,VQ`O1G1}OOQ!0Lf1G2P1G2POOOO,59o,59oO%,[Q!dO,59oOOOO-E<a-E<aOOQ!0Lh1G/X1G/XO%,cQ!0MxO7+'lOOQ!0Lh,5?_,5?_O%-VQMhO1G2gP%-^Q`O'#IsPOQ!0Lh-E<q-E<qO%-zQMjO,5?bOOQ!0Lh-E<t-E<tO%.mQMjO,5?dOOQ!0Lh-E<v-E<vO%.wQ!dO1G2xO%/OQ!dO'#CrO%/fQMhO'#KTO$$iQlO'#JwOOQ!0Lh1G2`1G2`O%/pQ`O'#IrO%0XQ`O,5@wO%0XQ`O,5@wO%0aQ`O,5@wO%0lQ`O,5@wOOQO1G2b1G2bO%0zQMjO1G2aO$*zQ`O'#K]O!,TQMhO1G2aO%1[Q(CWO'#ItO%1iQ`O,5@xO!&zQMhO,5@xO%1qQ!dO,5@xOOQ!0Lh1G2e1G2eO%4RQ!fO'#CiO%4]Q`O,5=QOOQ!0Lb,5=O,5=OO%4eQpO,5=OOOQ!0Lb,5=P,5=POCwQ`O,5=OO%4pQpO,5=OOOQ!0Lb,5=S,5=SO$*zQ`O,5=WOOQO,5?a,5?aOOQO-E<s-E<sOOQ!0Lp1G2i1G2iO#$ZQpO,5=OO$$iQlO,5=QO%5OQ`O,5=PO%5ZQpO,5=PO!,TQMhO'#IvO%6TQMjO1G2tO!,TQMhO'#IxO%6vQMjO1G2vO%7QQMjO1G5rO%7[QMjO1G5rOOQO,5?f,5?fOOQO-E<x-E<xOOQO1G.{1G.{O!,TQMhO1G5rO!,TQMhO1G5rO!=[QpO,59wO%[QlO,59wOOQ!0Lh,5<k,5<kO%7iQ`O1G2[O!,TQMhO1G2cO%7nQ!0MxO7+'nOOQ!0Lf7+'n7+'nO!$wQlO7+'nO%8bQ`O,5;`OOQ!0Lb,5?h,5?hOOQ!0Lb-E<z-E<zO%8gQ!dO'#K_O#'{Q`O7+(fO4UQ!fO7+(fO$DcQ`O7+(fO%8qQ!0MvO'#CiO%9UQ!0MvO,5=TO%9iQ`O,5=TO%9qQ`O,5=TOOQ!0Lb1G5p1G5pOOQ[7+$a7+$aO!COQ!0LrO7+$aO!CZQpO7+$aO!$wQlO7+&aO%9vQ`O'#JRO%:_Q`O,5AQOOQO1G3i1G3iO9kQ`O,5AQO%:_Q`O,5AQO%:gQ`O,5AQOOQO,5?n,5?nOOQO-E=Q-E=QOOQ!0Lf7+'U7+'UO%:lQ`O7+)RO9uQ!0LrO7+)RO9kQ`O7+)RO@zQ`O7+)RO%:qQ`O7+)ROOQ[7+)R7+)ROOQ[7+(q7+(qO%:vQ!0MvO7+(nO!&zQMhO7+(nO!EcQ`O7+(oOOQ[7+(o7+(oO!&zQMhO7+(oO%;QQ`O'#KcO%;]Q`O,5=mOOQO,5?j,5?jOOQO-E<|-E<|OOQ[7+(t7+(tO%<oQpO'#H[OOQ[1G3a1G3aO!&zQMhO1G3aO%[QlO1G3aO%<vQ`O1G3aO%=RQMhO1G3aO9uQ!0LrO1G3cO$%UQ`O1G3cO9`Q`O1G3cO!CZQpO1G3cO!CcQMhO1G3cO%=aQ`O'#JQO%=uQ`O,5AOO%=}QpO,5AOOOQ!0Lb1G3d1G3dOOQ[7+$V7+$VO@zQ`O7+$VO9uQ!0LrO7+$VO%>YQ`O7+$VO%[QlO1G6mO%[QlO1G6nO%>_Q!0LrO1G6mO%>iQlO1G3lO%>pQ`O1G3lO%>uQlO1G3lOOQ[7+)U7+)UO9uQ!0LrO7+)`O`QlO7+)bOOQ['#Ki'#KiOOQ['#JT'#JTO%>|QlO,5>aOOQ[,5>a,5>aO%[QlO'#HvO%?ZQ`O'#HxOOQ[,5>g,5>gO9eQ`O,5>gOOQ[,5>i,5>iOOQ[7+)k7+)kOOQ[7+)q7+)qOOQ[7+)u7+)uOOQ[7+)w7+)wO%?`QpO1G5}O%?zQ`O1G0zOOQO1G/s1G/sO%@VQ?MtO1G/sO?YQ`O1G/sO!)[QlO'#DmOOQO,5?Q,5?QOOQO-E<d-E<dOOQO,5?W,5?WOOQO-E<j-E<jO!CZQpO1G/sOOQO-E<f-E<fOOQ!0Ln1G0]1G0]OOQ!0Lf7+%u7+%uO#'{Q`O7+%uOOQ!0Lf7+&`7+&`O?YQ`O7+&`O!CZQpO7+&`OOQO7+%x7+%xO$AiQ!0MxO7+&XOOQO7+&X7+&XO%[QlO7+&XO%@aQ!0LrO7+&XO!COQ!0LrO7+%xO!CZQpO7+%xO%@lQ!0LrO7+&XO%@zQ!0MxO7++sO%[QlO7++sO%A[Q`O7++rO%A[Q`O7++rOOQO1G4t1G4tO9eQ`O1G4tO%AdQ`O1G4tOOQS7+%}7+%}O#'{Q`O<<LQO4UQ!fO<<LQO%ArQ`O<<LQOOQ[<<LQ<<LQO!&zQMhO<<LQO%[QlO<<LQO%AzQ`O<<LQO%BVQ!0MzO,5?bO%DbQ!0MzO,5?dO%FmQ!0MzO1G2aO%IOQ!0MzO1G2tO%KZQ!0MzO1G2vO%MfQ!fO,5?RO%[QlO,5?ROOQO-E<e-E<eO%MpQ`O1G6OOOQ!0Lf<<JV<<JVO%MxQ?MtO1G0uO&!PQ?MtO1G1QO&!WQ?MtO1G1QO&$XQ?MtO1G1QO&$`Q?MtO1G1QO&&aQ?MtO1G1QO&(bQ?MtO1G1QO&(iQ?MtO1G1QO&(pQ?MtO1G1QO&*qQ?MtO1G1QO&*xQ?MtO1G1QO&+PQ!0MxO<<JgO&,wQ?MtO1G1QO&-tQ?MvO1G1QO&.wQ?MvO'#JmO&0}Q?MtO1G1dO&1[Q?MtO1G0UO&1fQMjO,5?UOOQO-E<h-E<hO!)[QlO'#FrOOQO'#K['#K[OOQO1G1v1G1vO&1pQ`O1G1uO&1uQ?MtO,5?]OOOW7+'i7+'iOOOO1G/Z1G/ZO&2PQ!dO1G4yOOQ!0Lh7+(R7+(RP!&zQMhO,5?_O!,TQMhO7+(dO&2WQ`O,5?^O9eQ`O,5?^O$*zQ`O,5?^OOQO-E<p-E<pO&2fQ`O1G6cO&2fQ`O1G6cO&2nQ`O1G6cO&2yQMjO7+'{O&3ZQ!dO,5?`O&3eQ`O,5?`O!&zQMhO,5?`OOQO-E<r-E<rO&3jQ!dO1G6dO&3tQ`O1G6dO&3|Q`O1G2lO!&zQMhO1G2lOOQ!0Lb1G2j1G2jOOQ!0Lb1G2k1G2kO%4eQpO1G2jO!CZQpO1G2jOCwQ`O1G2jOOQ!0Lb1G2r1G2rO&4RQpO1G2jO&4aQ`O1G2lO$*zQ`O1G2kOCwQ`O1G2kO$$iQlO1G2lO&4iQ`O1G2kO&5]QMjO,5?bOOQ!0Lh-E<u-E<uO&6OQMjO,5?dOOQ!0Lh-E<w-E<wO!,TQMhO7++^O&6YQMjO7++^O&6dQMjO7++^OOQ!0Lh1G/c1G/cO&6qQ`O1G/cOOQ!0Lh7+'v7+'vO&6vQMjO7+'}O&7WQ!0MxO<<KYOOQ!0Lf<<KY<<KYO&7zQ`O1G0zO!&zQMhO'#I{O&8PQ`O,5@yO&:RQ!fO<<LQO!&zQMhO1G2oO&:YQ!0LrO1G2oOOQ[<<G{<<G{O!COQ!0LrO<<G{O&:kQ!0MxO<<I{OOQ!0Lf<<I{<<I{OOQO,5?m,5?mO&;_Q`O,5?mO&;dQ`O,5?mOOQO-E=P-E=PO&;rQ`O1G6lO&;rQ`O1G6lO9kQ`O1G6lO@zQ`O<<LmOOQ[<<Lm<<LmO&;zQ`O<<LmO9uQ!0LrO<<LmO9kQ`O<<LmOOQ[<<LY<<LYO%:vQ!0MvO<<LYOOQ[<<LZ<<LZO!EcQ`O<<LZO&<PQpO'#I}O&<[Q`O,5@}O!)[QlO,5@}OOQ[1G3X1G3XOOQO'#JP'#JPO9uQ!0LrO'#JPO&<dQpO,5=vOOQ[,5=v,5=vO&<kQpO'#EgO&<rQpO'#GfO&<wQ`O7+({O&<|Q`O7+({OOQ[7+({7+({O!&zQMhO7+({O%[QlO7+({O&=UQ`O7+({OOQ[7+(}7+(}O9uQ!0LrO7+(}O$%UQ`O7+(}O9`Q`O7+(}O!CZQpO7+(}O&=aQ`O,5?lOOQO-E=O-E=OOOQO'#H_'#H_O&=lQ`O1G6jO9uQ!0LrO<<GqOOQ[<<Gq<<GqO@zQ`O<<GqO&=tQ`O7+,XO&=yQ`O7+,YO%[QlO7+,XO%[QlO7+,YOOQ[7+)W7+)WO&>OQ`O7+)WO&>TQlO7+)WO&>[Q`O7+)WOOQ[<<Lz<<LzOOQ[<<L|<<L|OOQ[-E=R-E=ROOQ[1G3{1G3{O&>aQ`O,5>bOOQ[,5>d,5>dO&>fQ`O1G4RO9eQ`O7+&fO!)[QlO7+&fOOQO7+%_7+%_O&>kQ?MtO1G6[O?YQ`O7+%_OOQ!0Lf<<Ia<<IaOOQ!0Lf<<Iz<<IzO?YQ`O<<IzOOQO<<Is<<IsO$AiQ!0MxO<<IsO%[QlO<<IsOOQO<<Id<<IdO!COQ!0LrO<<IdO&>uQ!0LrO<<IsO&?QQ!0MxO<= _O&?bQ`O<= ^OOQO7+*`7+*`O9eQ`O7+*`OOQ[ANAlANAlO&?jQ!fOANAlO!&zQMhOANAlO#'{Q`OANAlO4UQ!fOANAlO&?qQ`OANAlO%[QlOANAlO&?yQ!0MzO7+'{O&B[Q!0MzO,5?bO&DgQ!0MzO,5?dO&FrQ!0MzO7+'}O&ITQ!fO1G4mO&I_Q?MtO7+&aO&KcQ?MvO,5=YO&MjQ?MvO,5=[O&MzQ?MvO,5=YO&N[Q?MvO,5=[O&NlQ?MvO,59uO'!rQ?MvO,5<lO'$uQ?MvO,5<nO''ZQ?MvO,5<|O')PQ?MtO7+'lO')^Q?MtO7+'nO')kQ`O,5<^OOQO7+'a7+'aOOQ!0Lh7+*e7+*eO')pQMjO<<LOOOQO1G4x1G4xO')wQ`O1G4xO'*SQ`O1G4xO'*bQ`O7++}O'*bQ`O7++}O!&zQMhO1G4zO'*jQ!dO1G4zO'*tQ`O7+,OO'*|Q`O7+(WO'+XQ!dO7+(WOOQ!0Lb7+(U7+(UOOQ!0Lb7+(V7+(VO!CZQpO7+(UOCwQ`O7+(UO'+cQ`O7+(WO!&zQMhO7+(WO$*zQ`O7+(VO'+hQ`O7+(WOCwQ`O7+(VO'+pQMjO<<NxO!,TQMhO<<NxOOQ!0Lh7+$}7+$}O'+zQ!dO,5?gOOQO-E<y-E<yO',UQ!0MvO7+(ZO!&zQMhO7+(ZOOQ[AN=gAN=gO9kQ`O1G5XOOQO1G5X1G5XO',fQ`O1G5XO',kQ`O7+,WO',kQ`O7+,WO9uQ!0LrOANBXO@zQ`OANBXOOQ[ANBXANBXO',sQ`OANBXOOQ[ANAtANAtOOQ[ANAuANAuO',xQ`O,5?iOOQO-E<{-E<{O'-TQ?MtO1G6iOOQO,5?k,5?kOOQO-E<}-E<}OOQ[1G3b1G3bO'-_Q`O,5=QOOQ[<<Lg<<LgO!&zQMhO<<LgO&<wQ`O<<LgO'-dQ`O<<LgO%[QlO<<LgOOQ[<<Li<<LiO9uQ!0LrO<<LiO$%UQ`O<<LiO9`Q`O<<LiO'-lQpO1G5WO'-wQ`O7+,UOOQ[AN=]AN=]O9uQ!0LrOAN=]OOQ[<= s<= sOOQ[<= t<= tO'.PQ`O<= sO'.UQ`O<= tOOQ[<<Lr<<LrO'.ZQ`O<<LrO'.`QlO<<LrOOQ[1G3|1G3|O?YQ`O7+)mO'.gQ`O<<JQO'.rQ?MtO<<JQOOQO<<Hy<<HyOOQ!0LfAN?fAN?fOOQOAN?_AN?_O$AiQ!0MxOAN?_OOQOAN?OAN?OO%[QlOAN?_OOQO<<Mz<<MzOOQ[G27WG27WO!&zQMhOG27WO#'{Q`OG27WO'.|Q!fOG27WO4UQ!fOG27WO'/TQ`OG27WO'/]Q?MtO<<JgO'/jQ?MvO1G2aO'1`Q?MvO,5?bO'3cQ?MvO,5?dO'5fQ?MvO1G2tO'7iQ?MvO1G2vO'9lQ?MtO<<KYO'9yQ?MtO<<I{OOQO1G1x1G1xO!,TQMhOANAjOOQO7+*d7+*dO':WQ`O7+*dO':cQ`O<= iO':kQ!dO7+*fOOQ!0Lb<<Kr<<KrO$*zQ`O<<KrOCwQ`O<<KrO':uQ`O<<KrO!&zQMhO<<KrOOQ!0Lb<<Kp<<KpO!CZQpO<<KpO';QQ!dO<<KrOOQ!0Lb<<Kq<<KqO';[Q`O<<KrO!&zQMhO<<KrO$*zQ`O<<KqO';aQMjOANDdO';kQ!0MvO<<KuOOQO7+*s7+*sO9kQ`O7+*sO';{Q`O<= rOOQ[G27sG27sO9uQ!0LrOG27sO@zQ`OG27sO!)[QlO1G5TO'<TQ`O7+,TO'<]Q`O1G2lO&<wQ`OANBROOQ[ANBRANBRO!&zQMhOANBRO'<bQ`OANBROOQ[ANBTANBTO9uQ!0LrOANBTO$%UQ`OANBTOOQO'#H`'#H`OOQO7+*r7+*rOOQ[G22wG22wOOQ[ANE_ANE_OOQ[ANE`ANE`OOQ[ANB^ANB^O'<jQ`OANB^OOQ[<<MX<<MXO!)[QlOAN?lOOQOG24yG24yO$AiQ!0MxOG24yO#'{Q`OLD,rOOQ[LD,rLD,rO!&zQMhOLD,rO'<oQ!fOLD,rO'<vQ?MvO7+'{O'>lQ?MvO,5?bO'@oQ?MvO,5?dO'BrQ?MvO7+'}O'DhQMjOG27UOOQO<<NO<<NOOOQ!0LbANA^ANA^O$*zQ`OANA^OCwQ`OANA^O'DxQ!dOANA^OOQ!0LbANA[ANA[O'EPQ`OANA^O!&zQMhOANA^O'E[Q!dOANA^OOQ!0LbANA]ANA]OOQO<<N_<<N_OOQ[LD-_LD-_O9uQ!0LrOLD-_O'EfQ?MtO7+*oOOQO'#Gg'#GgOOQ[G27mG27mO&<wQ`OG27mO!&zQMhOG27mOOQ[G27oG27oO9uQ!0LrOG27oOOQ[G27xG27xO'EpQ?MtOG25WOOQOLD*eLD*eOOQ[!$(!^!$(!^O#'{Q`O!$(!^O!&zQMhO!$(!^O'EzQ!0MzOG27UOOQ!0LbG26xG26xO$*zQ`OG26xO'H]Q`OG26xOCwQ`OG26xO'HhQ!dOG26xO!&zQMhOG26xOOQ[!$(!y!$(!yOOQ[LD-XLD-XO&<wQ`OLD-XOOQ[LD-ZLD-ZOOQ[!)9Ex!)9ExO#'{Q`O!)9ExOOQ!0LbLD,dLD,dO$*zQ`OLD,dOCwQ`OLD,dO'HoQ`OLD,dO'HzQ!dOLD,dOOQ[!$(!s!$(!sOOQ[!.K;d!.K;dO'IRQ?MvOG27UOOQ!0Lb!$(!O!$(!OO$*zQ`O!$(!OOCwQ`O!$(!OO'JwQ`O!$(!OOOQ!0Lb!)9Ej!)9EjO$*zQ`O!)9EjOCwQ`O!)9EjOOQ!0Lb!.K;U!.K;UO$*zQ`O!.K;UOOQ!0Lb!4/0p!4/0pO!)[QlO'#DzO1PQ`O'#EXO'KSQ!fO'#JsO'KZQ!L^O'#DvO'KbQlO'#EOO'KiQ!fO'#CiO'NPQ!fO'#CiO!)[QlO'#EQO'NaQlO,5;ZO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO,5;fO!)[QlO'#IqO(!dQ`O,5<jO!)[QlO,5;fO(!lQMhO,5;fO($VQMhO,5;fO!)[QlO,5;xO!&zQMhO'#GnO(!lQMhO'#GnO!&zQMhO'#GpO(!lQMhO'#GpO1SQ`O'#DZO1SQ`O'#DZO!&zQMhO'#GQO(!lQMhO'#GQO!&zQMhO'#GSO(!lQMhO'#GSO!&zQMhO'#GbO(!lQMhO'#GbO!)[QlO,5:jO($^QpO'#D_O!)[QlO,5@pO'NaQlO1G0uO($hQ?MtO'#CiO!)[QlO1G2QO!&zQMhO'#IvO(!lQMhO'#IvO!&zQMhO'#IxO(!lQMhO'#IxO($rQ!dO'#CrO!&zQMhO,5<uO(!lQMhO,5<uO'NaQlO1G2SO!)[QlO7+&{O!&zQMhO1G2aO(!lQMhO1G2aO!&zQMhO'#IvO(!lQMhO'#IvO!&zQMhO'#IxO(!lQMhO'#IxO!&zQMhO1G2cO(!lQMhO1G2cO'NaQlO7+'nO'NaQlO7+&aO!&zQMhOANAjO(!lQMhOANAjO(%VQ`O'#EoO(%[Q`O'#EoO(%dQ`O'#F^O(%iQ`O'#EzO(%nQ`O'#KUO(%yQ`O'#KSO(&UQ`O,5;ZO(&ZQMjO,5<fO(&bQ`O'#GZO(&gQ`O'#GZO(&lQ`O,5<fO(&tQ`O,5<hO(&|Q`O,5;ZO('UQ?MtO1G1aO(']Q`O,5<uO('bQ`O,5<uO('gQ`O,5<wO('lQ`O,5<wO('qQ`O1G2SO('vQ`O1G0uO('{QMjO<<LOO((SQMjO<<LOO((ZQMhO'#F}O9`Q`O'#F|OAuQ`O'#EnO!)[QlO,5;uO!3oQ`O'#GZO!3oQ`O'#GZO!3oQ`O'#G]O!3oQ`O'#G]O!,TQMhO7+(dO!,TQMhO7+(dO%.wQ!dO1G2xO%.wQ!dO1G2xO!&zQMhO,5=^O!&zQMhO,5=^",stateData:"()a~O'}OS(OOSTOS(PRQ~OPYOQYOSfOY!VOaqOdzOeyOl!POpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_XO!iuO!lZO!oYO!pYO!qYO!svO!uwO!xxO!|]O$X|O$oiO%i}O%k!QO%m!OO%n!OO%o!OO%r!RO%t!SO%w!TO%x!TO%z!UO&X!WO&_!XO&a!YO&c!ZO&e![O&h!]O&n!^O&t!_O&v!`O&x!aO&z!bO&|!cO(USO(WTO(ZUO(bVO(p[O~OWtO~P`OPYOQYOSfOd!jOe!iOpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_!eO!iuO!lZO!oYO!pYO!qYO!svO!u!gO!x!hO$X!kO$oiO(U!dO(WTO(ZUO(bVO(p[O~Oa!wOs!nO!S!oO!b!yO!c!vO!d!vO!|<VO#T!pO#U!pO#V!xO#W!pO#X!pO#[!zO#]!zO(V!lO(WTO(ZUO(f!mO(p!sO~O(P!{O~OP]XR]X[]Xa]Xj]Xr]X!Q]X!S]X!]]X!l]X!p]X#R]X#S]X#`]X#lfX#o]X#p]X#q]X#r]X#s]X#t]X#u]X#v]X#w]X#y]X#{]X#|]X$R]X'{]X(b]X(s]X(z]X({]X~O!g%SX~P(qO_!}O(W#PO(X!}O(Y#PO~O_#QO(Y#PO(Z#PO([#QO~Ox#SO!U#TO(c#TO(d#VO~OPYOQYOSfOd!jOe!iOpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_!eO!iuO!lZO!oYO!pYO!qYO!svO!u!gO!x!hO$X!kO$oiO(U<ZO(WTO(ZUO(bVO(p[O~O![#ZO!]#WO!Y(iP!Y(wP~P+}O!^#cO~P`OPYOQYOSfOd!jOe!iOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_!eO!iuO!lZO!oYO!pYO!qYO!svO!u!gO!x!hO$X!kO$oiO(WTO(ZUO(bVO(p[O~Op#mO![#iO!|]O#j#lO#k#iO(U<[O!k(tP~P.iO!l#oO(U#nO~O!x#sO!|]O%i#tO~O#l#uO~O!g#vO#l#uO~OP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!]$_O!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO#w$SO#y$UO#{$WO#|$XO(bVO(s$YO(z#|O({#}O~Oa(gX'{(gX'x(gX!k(gX!Y(gX!_(gX%j(gX!g(gX~P1qO#S$dO#`$eO$R$eOP(hXR(hX[(hXj(hXr(hX!Q(hX!S(hX!](hX!l(hX!p(hX#R(hX#o(hX#p(hX#q(hX#r(hX#s(hX#t(hX#u(hX#v(hX#w(hX#y(hX#{(hX#|(hX(b(hX(s(hX(z(hX({(hX!_(hX%j(hX~Oa(hX'{(hX'x(hX!Y(hX!k(hXv(hX!g(hX~P4UO#`$eO~O$^$hO$`$gO$g$mO~OSfO!_$nO$j$oO$l$qO~Oh%VOj%dOk%dOp%WOr%XOs$tOt$tOz%YO|%ZO!O%]O!S${O!_$|O!i%bO!l$xO#k%cO$X%`O$u%^O$w%_O$z%aO(U$sO(WTO(ZUO(b$uO(z$}O({%POg(_P~Ol%[O~P7eO!l%eO~O!S%hO!_%iO(U%gO~O!g%mO~Oa%nO'{%nO~O!Q%rO~P%[O(V!lO~P%[O%o%vO~P%[Oh%VO!l%eO(U%gO(V!lO~Oe%}O!l%eO(U%gO~Oj$RO~O!_&PO(U%gO(V!lO(WTO(ZUO`)XP~O!Q&SO!l&RO%k&VO&U&WO~P;SO!x#sO~O%t&YO!S)TX!_)TX(U)TX~O(U&ZO~Ol!PO!u&`O%k!QO%m!OO%n!OO%o!OO%r!RO%t!SO%w!TO%x!TO~Od&eOe&dO!x&bO%i&cO%|&aO~P<bOd&hOeyOl!PO!_&gO!u&`O!xxO!|]O%i}O%m!OO%n!OO%o!OO%r!RO%t!SO%w!TO%x!TO%z!UO~Ob&kO#`&nO%k&iO(V!lO~P=gO!l&oO!u&sO~O!l#oO~O!_XO~Oa%nO'y&{O'{%nO~Oa%nO'y'OO'{%nO~Oa%nO'y'QO'{%nO~O'x]X!Y]Xv]X!k]X&]]X!_]X%j]X!g]X~P(qO!b'`O!c'WO!d'WO(V!lO(WTO(ZUO~Os'UO!S'TO!['XO(f'SO!^(jP!^(yP~P@nOn'cO!_'aO(U%gO~Oe'hO!l%eO(U%gO~O!Q&SO!l&RO~Os!nO!S!oO!|<VO#T!pO#U!pO#W!pO#X!pO(V!lO(WTO(ZUO(f!mO(p!sO~O!b'nO!c'mO!d'mO#V!pO#['oO#]'oO~PBYOa%nOh%VO!g#vO!l%eO'{%nO(s'qO~O!p'uO#`'sO~PChOs!nO!S!oO(WTO(ZUO(f!mO(p!sO~O!_XOs(nX!S(nX!b(nX!c(nX!d(nX!|(nX#T(nX#U(nX#V(nX#W(nX#X(nX#[(nX#](nX(V(nX(W(nX(Z(nX(f(nX(p(nX~O!c'mO!d'mO(V!lO~PDWO(Q'yO(R'yO(S'{O~O_!}O(W'}O(X!}O(Y'}O~O_#QO(Y'}O(Z'}O([#QO~Ov(PO~P%[Ox#SO!U#TO(c#TO(d(SO~O![(UO!Y'XX!Y'_X!]'XX!]'_X~P+}O!](WO!Y(iX~OP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!](WO!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO#w$SO#y$UO#{$WO#|$XO(bVO(s$YO(z#|O({#}O~O!Y(iX~PHRO!Y(]O~O!Y(vX!](vX!g(vX!k(vX(s(vX~O#`(vX#l#dX!^(vX~PJUO#`(^O!Y(xX!](xX~O!](_O!Y(wX~O!Y(bO~O#`$eO~PJUO!^(cO~P`OR#zO!Q#yO!S#{O!l#xO(bVOP!na[!naj!nar!na!]!na!p!na#R!na#o!na#p!na#q!na#r!na#s!na#t!na#u!na#v!na#w!na#y!na#{!na#|!na(s!na(z!na({!na~Oa!na'{!na'x!na!Y!na!k!nav!na!_!na%j!na!g!na~PKlO!k(dO~O!g#vO#`(eO(s'qO!](uXa(uX'{(uX~O!k(uX~PNXO!S%hO!_%iO!|]O#j(jO#k(iO(U%gO~O!](kO!k(tX~O!k(mO~O!S%hO!_%iO#k(iO(U%gO~OP(hXR(hX[(hXj(hXr(hX!Q(hX!S(hX!](hX!l(hX!p(hX#R(hX#o(hX#p(hX#q(hX#r(hX#s(hX#t(hX#u(hX#v(hX#w(hX#y(hX#{(hX#|(hX(b(hX(s(hX(z(hX({(hX~O!g#vO!k(hX~P! uOR(oO!Q(nO!l#xO#S$dO!|!{a!S!{a~O!x!{a%i!{a!_!{a#j!{a#k!{a(U!{a~P!#vO!x(sO~OPYOQYOSfOd!jOe!iOpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_XO!iuO!lZO!oYO!pYO!qYO!svO!u!gO!x!hO$X!kO$oiO(U!dO(WTO(ZUO(bVO(p[O~Oh%VOp%WOr%XOs$tOt$tOz%YO|%ZO!O<sO!S${O!_$|O!i>UO!l$xO#k<yO$X%`O$u<uO$w<wO$z%aO(U(wO(WTO(ZUO(b$uO(z$}O({%PO~O#l(yO~O![({O!k(lP~P%[O(f(}O(p[O~O!S)PO!l#xO(f(}O(p[O~OP<UOQ<UOSfOd>QOe!iOpkOr<UOskOtkOzkO|<UO!O<UO!SWO!WkO!XkO!_!eO!i<XO!lZO!o<UO!p<UO!q<UO!s<YO!u<]O!x!hO$X!kO$o>OO(U)^O(WTO(ZUO(bVO(p[O~O!]$_Oa$ra'{$ra'x$ra!k$ra!Y$ra!_$ra%j$ra!g$ra~Ol)eO~P!&zOh%VOp%WOr%XOs$tOt$tOz%YO|%ZO!O%]O!S${O!_$|O!i%bO!l$xO#k%cO$X%`O$u%^O$w%_O$z%aO(U(wO(WTO(ZUO(b$uO(z$}O({%PO~Og(qP~P!,TO!Q)jO!g)iO!_$_X$[$_X$^$_X$`$_X$g$_X~O!g)iO!_(|X$[(|X$^(|X$`(|X$g(|X~O!Q)jO~P!.^O!Q)jO!_(|X$[(|X$^(|X$`(|X$g(|X~O!_)lO$[)pO$^)kO$`)kO$g)qO~O![)tO~P!)[O$^$hO$`$gO$g)xO~On${X!Q${X#S${X'z${X(z${X({${X~OgmXg${XnmX!]mX#`mX~P!0SOx)zO(c){O(d)}O~On*WO!Q*PO'z*QO(z$}O({%PO~Og*OO~P!1WOg*XO~Oh%VOr%XOs$tOt$tOz%YO|%ZO!O<sO!S*ZO!_*[O!i>UO!l$xO#k<yO$X%`O$u<uO$w<wO$z%aO(WTO(ZUO(b$uO(z$}O({%PO~Op*aO![*_O(U*YO!k)PP~P!1uO#l*bO~O!l*cO~Oh%VOp%WOr%XOs$tOt$tOz%YO|%ZO!O<sO!S${O!_$|O!i>UO!l$xO#k<yO$X%`O$u<uO$w<wO$z%aO(U*eO(WTO(ZUO(b$uO(z$}O({%PO~O![*hO!Y)QP~P!3tOr*tOs!nO!S*jO!b*rO!c*lO!d*lO!l*cO#[*sO%a*nO(V!lO(WTO(ZUO(f!mO~O!^*qO~P!5iO#S$dOn(aX!Q(aX'z(aX(z(aX({(aX!](aX#`(aX~Og(aX$P(aX~P!6kOn*yO#`*xOg(`X!](`X~O!]*zOg(_X~Oj%dOk%dOl%dO(U&ZOg(_P~Os*}O~Og*OO(U&ZO~O!l+TO~O(U(wO~Op+XO!S%hO![#iO!_%iO!|]O#j#lO#k#iO(U%gO!k(tP~O!g#vO#l+YO~O!S%hO![+[O!](_O!_%iO(U%gO!Y(wP~Os']O!S+_O![+^O(WTO(ZUO(f+]O~O!^(yP~P!9|O!]+`Oa)UX'{)UX~OP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO#w$SO#y$UO#{$WO#|$XO(bVO(s$YO(z#|O({#}O~Oa!ja!]!ja'{!ja'x!ja!Y!ja!k!jav!ja!_!ja%j!ja!g!ja~P!:tO(f(}O~OR#zO!Q#yO!S#{O!l#xO(bVOP!ra[!raj!rar!ra!]!ra!p!ra#R!ra#o!ra#p!ra#q!ra#r!ra#s!ra#t!ra#u!ra#v!ra#w!ra#y!ra#{!ra#|!ra(s!ra(z!ra({!ra~Oa!ra'{!ra'x!ra!Y!ra!k!rav!ra!_!ra%j!ra!g!ra~P!=aOR#zO!Q#yO!S#{O!l#xO(bVOP!ta[!taj!tar!ta!]!ta!p!ta#R!ta#o!ta#p!ta#q!ta#r!ta#s!ta#t!ta#u!ta#v!ta#w!ta#y!ta#{!ta#|!ta(s!ta(z!ta({!ta~Oa!ta'{!ta'x!ta!Y!ta!k!tav!ta!_!ta%j!ta!g!ta~P!?wOh%VOn+iO!_'aO%j+hO~O!g+kOa(^X!_(^X'{(^X!](^X~Oa%nO!_XO'{%nO~Oh%VO!l%eO~Oh%VO!l%eO(U%gO~O!g#vO#l(yO~Ob+vO%k+wO(U+sO(WTO(ZUO!^)YP~O!]+xO`)XX~O[+|O~O`+}O~O!_&PO(U%gO(V!lO`)XP~O%k,QO~P;SOh%VO#`,UO~Oh%VOn,XO!_$|O~O!_,ZO~O!Q,]O!_XO~O%o%vO~O!x,bO~Oe,gO~Ob,hO(U#nO(WTO(ZUO!^)WP~Oe%}O~O%k!QO(U&ZO~P=gO[,mO`,lO~OPYOQYOSfOdzOeyOpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!iuO!lZO!oYO!pYO!qYO!svO!xxO!|]O$oiO%i}O(WTO(ZUO(bVO(p[O~O!_!eO!u!gO$X!kO(U!dO~P!GOO`,lOa%nO'{%nO~OPYOQYOSfOd!jOe!iOpkOrYOskOtkOzkO|YO!OYO!SWO!WkO!XkO!_!eO!iuO!lZO!oYO!pYO!qYO!svO!x!hO$X!kO$oiO(U!dO(WTO(ZUO(bVO(p[O~Oa,rOl!OO!uwO%m!OO%n!OO%o!OO~P!IhO!l&oO~O&_,xO~O!_,zO~O&p,|O&r,}OP&maQ&maS&maY&maa&mad&mae&mal&map&mar&mas&mat&maz&ma|&ma!O&ma!S&ma!W&ma!X&ma!_&ma!i&ma!l&ma!o&ma!p&ma!q&ma!s&ma!u&ma!x&ma!|&ma$X&ma$o&ma%i&ma%k&ma%m&ma%n&ma%o&ma%r&ma%t&ma%w&ma%x&ma%z&ma&X&ma&_&ma&a&ma&c&ma&e&ma&h&ma&n&ma&t&ma&v&ma&x&ma&z&ma&|&ma'x&ma(U&ma(W&ma(Z&ma(b&ma(p&ma!^&ma&f&mab&ma&k&ma~O(U-SO~Oh!eX!]#iX!^#iX!g!RX!g!eX!l!eX~O!]!eX!^!eX~P#!nO!g-WOh(kX!](kX!^(kX!g(kX!l(kXr(kX(s(kX~Oh%VO!g-YO!l%eO!]!aX!^!aX~Os!nO!S!oO(WTO(ZUO(f!mO~OP<UOQ<UOSfOd>QOe!iOpkOr<UOskOtkOzkO|<UO!O<UO!SWO!WkO!XkO!_!eO!i<XO!lZO!o<UO!p<UO!q<UO!s<YO!u<]O!x!hO$X!kO$o>OO(WTO(ZUO(bVO(p[O~O(U=PO~P#$lO!]-^O!^(jX~O!^-`O~O!g-WO~O!]-aO!^(yX~O!^-cO~O!c-dO!d-dO(V!lO~P#$ZO!^-gO~P'_On-jO!_'aO~O!Y-oO~Os!{a!b!{a!c!{a!d!{a#T!{a#U!{a#V!{a#W!{a#X!{a#[!{a#]!{a(V!{a(W!{a(Z!{a(f!{a(p!{a~P!#vO!p-tO#`-rO~PChO!c-vO!d-vO(V!lO~PDWOa%nO#`-rO'{%nO~Oa%nO!g#vO#`-rO'{%nO~Oa%nO!g#vO!p-tO#`-rO'{%nO(s'qO~O(Q'yO(R'yO(S-{O~Ov-|O~O!Y'Xa!]'Xa~P!:tO![.QO!Y'XX!]'XX~P%[O!](WO!Y(ia~O!Y(ia~PHRO!](_O!Y(wa~O!S%hO![.UO!_%iO(U%gO!Y'_X!]'_X~O#`.WO!](ua!k(uaa(ua'{(ua~O!g#vO~P#,iO!](kO!k(ta~O!S%hO!_%iO#k.[O(U%gO~Op.aO!S%hO![.^O!_%iO!|]O#j.`O#k.^O(U%gO!]'bX!k'bX~OR.eO!l#xO~Oh%VOn.hO!_'aO%j.gO~Oa#ci!]#ci'{#ci'x#ci!Y#ci!k#civ#ci!_#ci%j#ci!g#ci~P!:tOn>[O!Q*PO'z*QO(z$}O({%PO~O#l#_aa#_a#`#_a'{#_a!]#_a!k#_a!_#_a!Y#_a~P#/eO#l(aXP(aXR(aX[(aXa(aXj(aXr(aX!S(aX!l(aX!p(aX#R(aX#o(aX#p(aX#q(aX#r(aX#s(aX#t(aX#u(aX#v(aX#w(aX#y(aX#{(aX#|(aX'{(aX(b(aX(s(aX!k(aX!Y(aX'x(aXv(aX!_(aX%j(aX!g(aX~P!6kO!].uO!k(lX~P!:tO!k.xO~O!Y.zO~OP$[OR#zO!Q#yO!S#{O!l#xO!p$[O(bVO[#nia#nij#nir#ni!]#ni#R#ni#p#ni#q#ni#r#ni#s#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni'{#ni(s#ni(z#ni({#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~O#o#ni~P#3TO#o$OO~P#3TOP$[OR#zOr$aO!Q#yO!S#{O!l#xO!p$[O#o$OO#p$PO#q$PO#r$PO(bVO[#nia#nij#ni!]#ni#R#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni'{#ni(s#ni(z#ni({#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~O#s#ni~P#5rO#s$QO~P#5rOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO(bVOa#ni!]#ni#y#ni#{#ni#|#ni'{#ni(s#ni(z#ni({#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~O#w#ni~P#8aOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO#w$SO(bVO({#}Oa#ni!]#ni#{#ni#|#ni'{#ni(s#ni(z#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~O#y$UO~P#:wO#y#ni~P#:wO#w$SO~P#8aOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#o$OO#p$PO#q$PO#r$PO#s$QO#t$RO#u$RO#v$bO#w$SO#y$UO(bVO(z#|O({#}Oa#ni!]#ni#|#ni'{#ni(s#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~O#{#ni~P#=mO#{$WO~P#=mOP]XR]X[]Xj]Xr]X!Q]X!S]X!l]X!p]X#R]X#S]X#`]X#lfX#o]X#p]X#q]X#r]X#s]X#t]X#u]X#v]X#w]X#y]X#{]X#|]X$R]X(b]X(s]X(z]X({]X!]]X!^]X~O$P]X~P#@[OP$[OR#zO[<mOj<bOr<kO!Q#yO!S#{O!l#xO!p$[O#R<bO#o<_O#p<`O#q<`O#r<`O#s<aO#t<bO#u<bO#v<lO#w<cO#y<eO#{<gO#|<hO(bVO(s$YO(z#|O({#}O~O$P.|O~P#BiO#S$dO#`<nO$R<nO$P(hX!^(hX~P! uOa'ea!]'ea'{'ea'x'ea!k'ea!Y'eav'ea!_'ea%j'ea!g'ea~P!:tO[#nia#nij#nir#ni!]#ni#R#ni#s#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni'{#ni(s#ni'x#ni!Y#ni!k#niv#ni!_#ni%j#ni!g#ni~OP$[OR#zO!Q#yO!S#{O!l#xO!p$[O#o$OO#p$PO#q$PO#r$PO(bVO(z#ni({#ni~P#EkOn>[O!Q*PO'z*QO(z$}O({%POP#niR#ni!S#ni!l#ni!p#ni#o#ni#p#ni#q#ni#r#ni(b#ni~P#EkO!]/QOg(qX~P!1WOg/SO~Oa$Qi!]$Qi'{$Qi'x$Qi!Y$Qi!k$Qiv$Qi!_$Qi%j$Qi!g$Qi~P!:tO$^/TO$`/TO~O$^/UO$`/UO~O!g)iO#`/VO!_$dX$[$dX$^$dX$`$dX$g$dX~O![/WO~O!_)lO$[/YO$^)kO$`)kO$g/ZO~O!]<iO!^(gX~P#BiO!^/[O~O!g)iO$g(|X~O$g/^O~Ov/_O~P!&zOx)zO(c){O(d/bO~O!S/eO~O(z$}On%ba!Q%ba'z%ba({%ba!]%ba#`%ba~Og%ba$P%ba~P#LmO({%POn%da!Q%da'z%da(z%da!]%da#`%da~Og%da$P%da~P#M`O!]fX!gfX!kfX!k${X(sfX~P!0SOp%WO![/nO!](_O(U/mO!Y(wP!Y)QP~P!1uOr*tO!b*rO!c*lO!d*lO!l*cO#[*sO%a*nO(V!lO(WTO(ZUO~Os'UO!S/oO![+^O!^*qO(f<|O!^(yP~P#N|O!k/pO~P#/eO!]/qO!g#vO(s'qO!k)PX~O!k/vO~OnoX!QoX'zoX(zoX({oX~O!g#vO!koX~P$!pOp/xO!S%hO![*_O!_%iO(U%gO!k)PP~O#l/yO~O!Y${X!]${X!g%SX~P!0SO!]/zO!Y)QX~P#/eO!g/|O~O!Y0OO~OpkO(U0PO~P.iOh%VOr0UO!g#vO!l%eO(s'qO~O!g+kO~Oa%nO!]0YO'{%nO~O!^0[O~P!5iO!c0]O!d0]O(V!lO~P#$ZOs!nO!S0^O(WTO(ZUO(f!mO~O#[0`O~Og%ba!]%ba#`%ba$P%ba~P!1WOg%da!]%da#`%da$P%da~P!1WOj%dOk%dOl%dO(U&ZOg'nX!]'nX~O!]*zOg(_a~Og0iO~On0kO#`0jOg(`a!](`a~OR0lO!Q0lO!S0mO#S$dOn}a'z}a(z}a({}a!]}a#`}a~Og}a$P}a~P$(TO!Q*PO'z*QOn$ta(z$ta({$ta!]$ta#`$ta~Og$ta$P$ta~P$)PO!Q*PO'z*QOn$va(z$va({$va!]$va#`$va~Og$va$P$va~P$)rO#l0pO~Og%Ua!]%Ua#`%Ua$P%Ua~P!1WO!g#vO~O#l0sO~O!]#iX!^#iX!g!RX~O!]+`Oa)Ua'{)Ua~OR#zO!Q#yO!S#{O!l#xO(bVOP!ri[!rij!rir!ri!]!ri!p!ri#R!ri#o!ri#p!ri#q!ri#r!ri#s!ri#t!ri#u!ri#v!ri#w!ri#y!ri#{!ri#|!ri(s!ri(z!ri({!ri~Oa!ri'{!ri'x!ri!Y!ri!k!riv!ri!_!ri%j!ri!g!ri~P$+lOh%VOr%XOs$tOt$tOz%YO|%ZO!O<sO!S${O!_$|O!i>UO!l$xO#k<yO$X%`O$u<uO$w<wO$z%aO(WTO(ZUO(b$uO(z$}O({%PO~Op0|O%^0}O(U0{O~P$.SO!g+kOa(^a!_(^a'{(^a!](^a~O#l1TO~O[]X!]fX!^fX~O!]1UO!^)YX~O!^1WO~O[1XO~Ob1ZO(U+sO(WTO(ZUO~O!_&PO(U%gO`'vX!]'vX~O!]+xO`)Xa~O!k1^O~P!:tO[1aO~O`1bO~O#`1gO~On1jO!_$|O~O(f(}O!^)VP~Oh%VOn1sO!_1pO%j1rO~O[1}O!]1{O!^)WX~O!^2OO~O`2QOa%nO'{%nO~O(U#nO(WTO(ZUO~O#S$dO#`$eO$R$eOP(hXR(hX[(hXr(hX!Q(hX!S(hX!](hX!l(hX!p(hX#R(hX#o(hX#p(hX#q(hX#r(hX#s(hX#t(hX#u(hX#v(hX#w(hX#y(hX#{(hX#|(hX(b(hX(s(hX(z(hX({(hX~Oj2TO&]2UOa(hX~P$3mOj2TO#`$eO&]2UO~Oa2WO~P%[Oa2YO~O&f2]OP&diQ&diS&diY&dia&did&die&dil&dip&dir&dis&dit&diz&di|&di!O&di!S&di!W&di!X&di!_&di!i&di!l&di!o&di!p&di!q&di!s&di!u&di!x&di!|&di$X&di$o&di%i&di%k&di%m&di%n&di%o&di%r&di%t&di%w&di%x&di%z&di&X&di&_&di&a&di&c&di&e&di&h&di&n&di&t&di&v&di&x&di&z&di&|&di'x&di(U&di(W&di(Z&di(b&di(p&di!^&dib&di&k&di~Ob2cO!^2aO&k2bO~P`O!_XO!l2eO~O&r,}OP&miQ&miS&miY&mia&mid&mie&mil&mip&mir&mis&mit&miz&mi|&mi!O&mi!S&mi!W&mi!X&mi!_&mi!i&mi!l&mi!o&mi!p&mi!q&mi!s&mi!u&mi!x&mi!|&mi$X&mi$o&mi%i&mi%k&mi%m&mi%n&mi%o&mi%r&mi%t&mi%w&mi%x&mi%z&mi&X&mi&_&mi&a&mi&c&mi&e&mi&h&mi&n&mi&t&mi&v&mi&x&mi&z&mi&|&mi'x&mi(U&mi(W&mi(Z&mi(b&mi(p&mi!^&mi&f&mib&mi&k&mi~O!Y2kO~O!]!aa!^!aa~P#BiOs!nO!S!oO![2pO(f!mO!]'YX!^'YX~P@nO!]-^O!^(ja~O!]'`X!^'`X~P!9|O!]-aO!^(ya~O!^2wO~P'_Oa%nO#`3QO'{%nO~Oa%nO!g#vO#`3QO'{%nO~Oa%nO!g#vO!p3UO#`3QO'{%nO(s'qO~Oa%nO'{%nO~P!:tO!]$_Ov$ra~O!Y'Xi!]'Xi~P!:tO!](WO!Y(ii~O!](_O!Y(wi~O!Y(xi!](xi~P!:tO!](ui!k(uia(ui'{(ui~P!:tO#`3WO!](ui!k(uia(ui'{(ui~O!](kO!k(ti~O!S%hO!_%iO!|]O#j3]O#k3[O(U%gO~O!S%hO!_%iO#k3[O(U%gO~On3dO!_'aO%j3cO~Oh%VOn3dO!_'aO%j3cO~O#l%baP%baR%ba[%baa%baj%bar%ba!S%ba!l%ba!p%ba#R%ba#o%ba#p%ba#q%ba#r%ba#s%ba#t%ba#u%ba#v%ba#w%ba#y%ba#{%ba#|%ba'{%ba(b%ba(s%ba!k%ba!Y%ba'x%bav%ba!_%ba%j%ba!g%ba~P#LmO#l%daP%daR%da[%daa%daj%dar%da!S%da!l%da!p%da#R%da#o%da#p%da#q%da#r%da#s%da#t%da#u%da#v%da#w%da#y%da#{%da#|%da'{%da(b%da(s%da!k%da!Y%da'x%dav%da!_%da%j%da!g%da~P#M`O#l%baP%baR%ba[%baa%baj%bar%ba!S%ba!]%ba!l%ba!p%ba#R%ba#o%ba#p%ba#q%ba#r%ba#s%ba#t%ba#u%ba#v%ba#w%ba#y%ba#{%ba#|%ba'{%ba(b%ba(s%ba!k%ba!Y%ba'x%ba#`%bav%ba!_%ba%j%ba!g%ba~P#/eO#l%daP%daR%da[%daa%daj%dar%da!S%da!]%da!l%da!p%da#R%da#o%da#p%da#q%da#r%da#s%da#t%da#u%da#v%da#w%da#y%da#{%da#|%da'{%da(b%da(s%da!k%da!Y%da'x%da#`%dav%da!_%da%j%da!g%da~P#/eO#l}aP}a[}aa}aj}ar}a!l}a!p}a#R}a#o}a#p}a#q}a#r}a#s}a#t}a#u}a#v}a#w}a#y}a#{}a#|}a'{}a(b}a(s}a!k}a!Y}a'x}av}a!_}a%j}a!g}a~P$(TO#l$taP$taR$ta[$taa$taj$tar$ta!S$ta!l$ta!p$ta#R$ta#o$ta#p$ta#q$ta#r$ta#s$ta#t$ta#u$ta#v$ta#w$ta#y$ta#{$ta#|$ta'{$ta(b$ta(s$ta!k$ta!Y$ta'x$tav$ta!_$ta%j$ta!g$ta~P$)PO#l$vaP$vaR$va[$vaa$vaj$var$va!S$va!l$va!p$va#R$va#o$va#p$va#q$va#r$va#s$va#t$va#u$va#v$va#w$va#y$va#{$va#|$va'{$va(b$va(s$va!k$va!Y$va'x$vav$va!_$va%j$va!g$va~P$)rO#l%UaP%UaR%Ua[%Uaa%Uaj%Uar%Ua!S%Ua!]%Ua!l%Ua!p%Ua#R%Ua#o%Ua#p%Ua#q%Ua#r%Ua#s%Ua#t%Ua#u%Ua#v%Ua#w%Ua#y%Ua#{%Ua#|%Ua'{%Ua(b%Ua(s%Ua!k%Ua!Y%Ua'x%Ua#`%Uav%Ua!_%Ua%j%Ua!g%Ua~P#/eOa#cq!]#cq'{#cq'x#cq!Y#cq!k#cqv#cq!_#cq%j#cq!g#cq~P!:tO![3lO!]'ZX!k'ZX~P%[O!].uO!k(la~O!].uO!k(la~P!:tO!Y3oO~O$P!na!^!na~PKlO$P!ja!]!ja!^!ja~P#BiO$P!ra!^!ra~P!=aO$P!ta!^!ta~P!?wOg'^X!]'^X~P!,TO!]/QOg(qa~OSfO!_4TO$e4UO~O!^4YO~Ov4ZO~P#/eOa$nq!]$nq'{$nq'x$nq!Y$nq!k$nqv$nq!_$nq%j$nq!g$nq~P!:tO!Y4]O~P!&zO!S4^O~O!Q*PO'z*QO({%POn'ja(z'ja!]'ja#`'ja~Og'ja$P'ja~P%-cO!Q*PO'z*QOn'la(z'la({'la!]'la#`'la~Og'la$P'la~P%.UO(s$YO~P#/eO!YfX!Y${X!]fX!]${X!g%SX#`fX~P!0SOp%WO(U=VO~P!1uOp4bO!S%hO![4aO!_%iO(U%gO!]'fX!k'fX~O!]/qO!k)Pa~O!]/qO!g#vO!k)Pa~O!]/qO!g#vO(s'qO!k)Pa~Og$}i!]$}i#`$}i$P$}i~P!1WO![4jO!Y'hX!]'hX~P!3tO!]/zO!Y)Qa~O!]/zO!Y)Qa~P#/eOP]XR]X[]Xj]Xr]X!Q]X!S]X!Y]X!]]X!l]X!p]X#R]X#S]X#`]X#lfX#o]X#p]X#q]X#r]X#s]X#t]X#u]X#v]X#w]X#y]X#{]X#|]X$R]X(b]X(s]X(z]X({]X~Oj%ZX!g%ZX~P%1{Oj4oO!g#vO~Oh%VO!g#vO!l%eO~Oh%VOr4tO!l%eO(s'qO~Or4yO!g#vO(s'qO~Os!nO!S4zO(WTO(ZUO(f!mO~O(z$}On%bi!Q%bi'z%bi({%bi!]%bi#`%bi~Og%bi$P%bi~P%5lO({%POn%di!Q%di'z%di(z%di!]%di#`%di~Og%di$P%di~P%6_Og(`i!](`i~P!1WO#`5QOg(`i!](`i~P!1WO!k5VO~Oa$pq!]$pq'{$pq'x$pq!Y$pq!k$pqv$pq!_$pq%j$pq!g$pq~P!:tO!Y5ZO~O!]5[O!_)RX~P#/eOa${X!_${X%_]X'{${X!]${X~P!0SO%_5_OaoX!_oX'{oX!]oX~P$!pOp5`O(U#nO~O%_5_O~Ob5fO%k5gO(U+sO(WTO(ZUO!]'uX!^'uX~O!]1UO!^)Ya~O[5kO~O`5lO~O[5pO~Oa%nO'{%nO~P#/eO!]5uO#`5wO!^)VX~O!^5xO~Or6OOs!nO!S*jO!b!yO!c!vO!d!vO!|<VO#T!pO#U!pO#V!pO#W!pO#X!pO#[5}O#]!zO(V!lO(WTO(ZUO(f!mO(p!sO~O!^5|O~P%;bOn6TO!_1pO%j6SO~Oh%VOn6TO!_1pO%j6SO~Ob6[O(U#nO(WTO(ZUO!]'tX!^'tX~O!]1{O!^)Wa~O(WTO(ZUO(f6^O~O`6bO~Oj6eO&]6fO~PNXO!k6gO~P%[Oa6iO~Oa6iO~P%[Ob2cO!^6nO&k2bO~P`O!g6pO~O!g6rOh(ki!](ki!^(ki!g(ki!l(kir(ki(s(ki~O#`6sO!]#hi!^#hi~O!]!ai!^!ai~P#BiOa%nO#`6|O'{%nO~Oa%nO!g#vO#`6|O'{%nO~O!](uq!k(uqa(uq'{(uq~P!:tO!](kO!k(tq~O!S%hO!_%iO#k7TO(U%gO~O!_'aO%j7WO~On7[O!_'aO%j7WO~O#l'jaP'jaR'ja['jaa'jaj'jar'ja!S'ja!l'ja!p'ja#R'ja#o'ja#p'ja#q'ja#r'ja#s'ja#t'ja#u'ja#v'ja#w'ja#y'ja#{'ja#|'ja'{'ja(b'ja(s'ja!k'ja!Y'ja'x'jav'ja!_'ja%j'ja!g'ja~P%-cO#l'laP'laR'la['laa'laj'lar'la!S'la!l'la!p'la#R'la#o'la#p'la#q'la#r'la#s'la#t'la#u'la#v'la#w'la#y'la#{'la#|'la'{'la(b'la(s'la!k'la!Y'la'x'lav'la!_'la%j'la!g'la~P%.UO#l$}iP$}iR$}i[$}ia$}ij$}ir$}i!S$}i!]$}i!l$}i!p$}i#R$}i#o$}i#p$}i#q$}i#r$}i#s$}i#t$}i#u$}i#v$}i#w$}i#y$}i#{$}i#|$}i'{$}i(b$}i(s$}i!k$}i!Y$}i'x$}i#`$}iv$}i!_$}i%j$}i!g$}i~P#/eO#l%biP%biR%bi[%bia%bij%bir%bi!S%bi!l%bi!p%bi#R%bi#o%bi#p%bi#q%bi#r%bi#s%bi#t%bi#u%bi#v%bi#w%bi#y%bi#{%bi#|%bi'{%bi(b%bi(s%bi!k%bi!Y%bi'x%biv%bi!_%bi%j%bi!g%bi~P%5lO#l%diP%diR%di[%dia%dij%dir%di!S%di!l%di!p%di#R%di#o%di#p%di#q%di#r%di#s%di#t%di#u%di#v%di#w%di#y%di#{%di#|%di'{%di(b%di(s%di!k%di!Y%di'x%div%di!_%di%j%di!g%di~P%6_O!]'Za!k'Za~P!:tO!].uO!k(li~O$P#ci!]#ci!^#ci~P#BiOP$[OR#zO!Q#yO!S#{O!l#xO!p$[O(bVO[#nij#nir#ni#R#ni#p#ni#q#ni#r#ni#s#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni$P#ni(s#ni(z#ni({#ni!]#ni!^#ni~O#o#ni~P%NVO#o<_O~P%NVOP$[OR#zOr<kO!Q#yO!S#{O!l#xO!p$[O#o<_O#p<`O#q<`O#r<`O(bVO[#nij#ni#R#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni$P#ni(s#ni(z#ni({#ni!]#ni!^#ni~O#s#ni~P&!_O#s<aO~P&!_OP$[OR#zO[<mOj<bOr<kO!Q#yO!S#{O!l#xO!p$[O#R<bO#o<_O#p<`O#q<`O#r<`O#s<aO#t<bO#u<bO#v<lO(bVO#y#ni#{#ni#|#ni$P#ni(s#ni(z#ni({#ni!]#ni!^#ni~O#w#ni~P&$gOP$[OR#zO[<mOj<bOr<kO!Q#yO!S#{O!l#xO!p$[O#R<bO#o<_O#p<`O#q<`O#r<`O#s<aO#t<bO#u<bO#v<lO#w<cO(bVO({#}O#{#ni#|#ni$P#ni(s#ni(z#ni!]#ni!^#ni~O#y<eO~P&&hO#y#ni~P&&hO#w<cO~P&$gOP$[OR#zO[<mOj<bOr<kO!Q#yO!S#{O!l#xO!p$[O#R<bO#o<_O#p<`O#q<`O#r<`O#s<aO#t<bO#u<bO#v<lO#w<cO#y<eO(bVO(z#|O({#}O#|#ni$P#ni(s#ni!]#ni!^#ni~O#{#ni~P&(wO#{<gO~P&(wOa#}y!]#}y'{#}y'x#}y!Y#}y!k#}yv#}y!_#}y%j#}y!g#}y~P!:tO[#nij#nir#ni#R#ni#s#ni#t#ni#u#ni#v#ni#w#ni#y#ni#{#ni#|#ni$P#ni(s#ni!]#ni!^#ni~OP$[OR#zO!Q#yO!S#{O!l#xO!p$[O#o<_O#p<`O#q<`O#r<`O(bVO(z#ni({#ni~P&+sOn>]O!Q*PO'z*QO(z$}O({%POP#niR#ni!S#ni!l#ni!p#ni#o#ni#p#ni#q#ni#r#ni(b#ni~P&+sO#S$dOP(aXR(aX[(aXj(aXn(aXr(aX!Q(aX!S(aX!l(aX!p(aX#R(aX#o(aX#p(aX#q(aX#r(aX#s(aX#t(aX#u(aX#v(aX#w(aX#y(aX#{(aX#|(aX$P(aX'z(aX(b(aX(s(aX(z(aX({(aX!](aX!^(aX~O$P$Qi!]$Qi!^$Qi~P#BiO$P!ri!^!ri~P$+lOg'^a!]'^a~P!1WO!^7nO~O!]'ea!^'ea~P#BiO!Y7oO~P#/eO!g#vO(s'qO!]'fa!k'fa~O!]/qO!k)Pi~O!]/qO!g#vO!k)Pi~Og$}q!]$}q#`$}q$P$}q~P!1WO!Y'ha!]'ha~P#/eO!g7vO~O!]/zO!Y)Qi~P#/eO!]/zO!Y)Qi~O!Y7yO~Oh%VOr8OO!l%eO(s'qO~Oj8QO!g#vO~Or8TO!g#vO(s'qO~O!Q*PO'z*QO({%POn'ka(z'ka!]'ka#`'ka~Og'ka$P'ka~P&4tO!Q*PO'z*QOn'ma(z'ma({'ma!]'ma#`'ma~Og'ma$P'ma~P&5gOg(`q!](`q~P!1WO#`8VOg(`q!](`q~P!1WO!Y8WO~Og%Pq!]%Pq#`%Pq$P%Pq~P!1WOa$py!]$py'{$py'x$py!Y$py!k$pyv$py!_$py%j$py!g$py~P!:tO!g6rO~O!]5[O!_)Ra~O!_'aOP$UaR$Ua[$Uaj$Uar$Ua!Q$Ua!S$Ua!]$Ua!l$Ua!p$Ua#R$Ua#o$Ua#p$Ua#q$Ua#r$Ua#s$Ua#t$Ua#u$Ua#v$Ua#w$Ua#y$Ua#{$Ua#|$Ua(b$Ua(s$Ua(z$Ua({$Ua~O%j7WO~P&8XO%_8[Oa%]i!_%]i'{%]i!]%]i~Oa#cy!]#cy'{#cy'x#cy!Y#cy!k#cyv#cy!_#cy%j#cy!g#cy~P!:tO[8^O~Ob8`O(U+sO(WTO(ZUO~O!]1UO!^)Yi~O`8dO~O(f(}O!]'qX!^'qX~O!]5uO!^)Va~O!^8nO~P%;bO(p!sO~P$%zO#[8oO~O!_1pO~O!_1pO%j8qO~On8tO!_1pO%j8qO~O[8yO!]'ta!^'ta~O!]1{O!^)Wi~O!k8}O~O!k9OO~O!k9RO~O!k9RO~P%[Oa9TO~O!g9UO~O!k9VO~O!](xi!^(xi~P#BiOa%nO#`9_O'{%nO~O!](uy!k(uya(uy'{(uy~P!:tO!](kO!k(ty~O%j9bO~P&8XO!_'aO%j9bO~O#l$}qP$}qR$}q[$}qa$}qj$}qr$}q!S$}q!]$}q!l$}q!p$}q#R$}q#o$}q#p$}q#q$}q#r$}q#s$}q#t$}q#u$}q#v$}q#w$}q#y$}q#{$}q#|$}q'{$}q(b$}q(s$}q!k$}q!Y$}q'x$}q#`$}qv$}q!_$}q%j$}q!g$}q~P#/eO#l'kaP'kaR'ka['kaa'kaj'kar'ka!S'ka!l'ka!p'ka#R'ka#o'ka#p'ka#q'ka#r'ka#s'ka#t'ka#u'ka#v'ka#w'ka#y'ka#{'ka#|'ka'{'ka(b'ka(s'ka!k'ka!Y'ka'x'kav'ka!_'ka%j'ka!g'ka~P&4tO#l'maP'maR'ma['maa'maj'mar'ma!S'ma!l'ma!p'ma#R'ma#o'ma#p'ma#q'ma#r'ma#s'ma#t'ma#u'ma#v'ma#w'ma#y'ma#{'ma#|'ma'{'ma(b'ma(s'ma!k'ma!Y'ma'x'mav'ma!_'ma%j'ma!g'ma~P&5gO#l%PqP%PqR%Pq[%Pqa%Pqj%Pqr%Pq!S%Pq!]%Pq!l%Pq!p%Pq#R%Pq#o%Pq#p%Pq#q%Pq#r%Pq#s%Pq#t%Pq#u%Pq#v%Pq#w%Pq#y%Pq#{%Pq#|%Pq'{%Pq(b%Pq(s%Pq!k%Pq!Y%Pq'x%Pq#`%Pqv%Pq!_%Pq%j%Pq!g%Pq~P#/eO!]'Zi!k'Zi~P!:tO$P#cq!]#cq!^#cq~P#BiO(z$}OP%baR%ba[%baj%bar%ba!S%ba!l%ba!p%ba#R%ba#o%ba#p%ba#q%ba#r%ba#s%ba#t%ba#u%ba#v%ba#w%ba#y%ba#{%ba#|%ba$P%ba(b%ba(s%ba!]%ba!^%ba~On%ba!Q%ba'z%ba({%ba~P&IlO({%POP%daR%da[%daj%dar%da!S%da!l%da!p%da#R%da#o%da#p%da#q%da#r%da#s%da#t%da#u%da#v%da#w%da#y%da#{%da#|%da$P%da(b%da(s%da!]%da!^%da~On%da!Q%da'z%da(z%da~P&KsOn>]O!Q*PO'z*QO({%PO~P&IlOn>]O!Q*PO'z*QO(z$}O~P&KsOR0lO!Q0lO!S0mO#S$dOP}a[}aj}an}ar}a!l}a!p}a#R}a#o}a#p}a#q}a#r}a#s}a#t}a#u}a#v}a#w}a#y}a#{}a#|}a$P}a'z}a(b}a(s}a(z}a({}a!]}a!^}a~O!Q*PO'z*QOP$taR$ta[$taj$tan$tar$ta!S$ta!l$ta!p$ta#R$ta#o$ta#p$ta#q$ta#r$ta#s$ta#t$ta#u$ta#v$ta#w$ta#y$ta#{$ta#|$ta$P$ta(b$ta(s$ta(z$ta({$ta!]$ta!^$ta~O!Q*PO'z*QOP$vaR$va[$vaj$van$var$va!S$va!l$va!p$va#R$va#o$va#p$va#q$va#r$va#s$va#t$va#u$va#v$va#w$va#y$va#{$va#|$va$P$va(b$va(s$va(z$va({$va!]$va!^$va~On>]O!Q*PO'z*QO(z$}O({%PO~OP%UaR%Ua[%Uaj%Uar%Ua!S%Ua!l%Ua!p%Ua#R%Ua#o%Ua#p%Ua#q%Ua#r%Ua#s%Ua#t%Ua#u%Ua#v%Ua#w%Ua#y%Ua#{%Ua#|%Ua$P%Ua(b%Ua(s%Ua!]%Ua!^%Ua~P'&xO$P$nq!]$nq!^$nq~P#BiO$P$pq!]$pq!^$pq~P#BiO!^9oO~O$P9pO~P!1WO!g#vO!]'fi!k'fi~O!g#vO(s'qO!]'fi!k'fi~O!]/qO!k)Pq~O!Y'hi!]'hi~P#/eO!]/zO!Y)Qq~Or9wO!g#vO(s'qO~O[9yO!Y9xO~P#/eO!Y9xO~Oj:PO!g#vO~Og(`y!](`y~P!1WO!]'oa!_'oa~P#/eOa%]q!_%]q'{%]q!]%]q~P#/eO[:UO~O!]1UO!^)Yq~O`:YO~O#`:ZO!]'qa!^'qa~O!]5uO!^)Vi~P#BiO!S:]O~O!_1pO%j:`O~O(WTO(ZUO(f:eO~O!]1{O!^)Wq~O!k:hO~O!k:iO~O!k:jO~O!k:jO~P%[O#`:mO!]#hy!^#hy~O!]#hy!^#hy~P#BiO%j:rO~P&8XO!_'aO%j:rO~O$P#}y!]#}y!^#}y~P#BiOP$}iR$}i[$}ij$}ir$}i!S$}i!l$}i!p$}i#R$}i#o$}i#p$}i#q$}i#r$}i#s$}i#t$}i#u$}i#v$}i#w$}i#y$}i#{$}i#|$}i$P$}i(b$}i(s$}i!]$}i!^$}i~P'&xO!Q*PO'z*QO({%POP'jaR'ja['jaj'jan'jar'ja!S'ja!l'ja!p'ja#R'ja#o'ja#p'ja#q'ja#r'ja#s'ja#t'ja#u'ja#v'ja#w'ja#y'ja#{'ja#|'ja$P'ja(b'ja(s'ja(z'ja!]'ja!^'ja~O!Q*PO'z*QOP'laR'la['laj'lan'lar'la!S'la!l'la!p'la#R'la#o'la#p'la#q'la#r'la#s'la#t'la#u'la#v'la#w'la#y'la#{'la#|'la$P'la(b'la(s'la(z'la({'la!]'la!^'la~O(z$}OP%biR%bi[%bij%bin%bir%bi!Q%bi!S%bi!l%bi!p%bi#R%bi#o%bi#p%bi#q%bi#r%bi#s%bi#t%bi#u%bi#v%bi#w%bi#y%bi#{%bi#|%bi$P%bi'z%bi(b%bi(s%bi({%bi!]%bi!^%bi~O({%POP%diR%di[%dij%din%dir%di!Q%di!S%di!l%di!p%di#R%di#o%di#p%di#q%di#r%di#s%di#t%di#u%di#v%di#w%di#y%di#{%di#|%di$P%di'z%di(b%di(s%di(z%di!]%di!^%di~O$P$py!]$py!^$py~P#BiO$P#cy!]#cy!^#cy~P#BiO!g#vO!]'fq!k'fq~O!]/qO!k)Py~O!Y'hq!]'hq~P#/eOr:|O!g#vO(s'qO~O[;QO!Y;PO~P#/eO!Y;PO~Og(`!R!](`!R~P!1WOa%]y!_%]y'{%]y!]%]y~P#/eO!]1UO!^)Yy~O!]5uO!^)Vq~O(U;XO~O!_1pO%j;[O~O!k;_O~O%j;dO~P&8XOP$}qR$}q[$}qj$}qr$}q!S$}q!l$}q!p$}q#R$}q#o$}q#p$}q#q$}q#r$}q#s$}q#t$}q#u$}q#v$}q#w$}q#y$}q#{$}q#|$}q$P$}q(b$}q(s$}q!]$}q!^$}q~P'&xO!Q*PO'z*QO({%POP'kaR'ka['kaj'kan'kar'ka!S'ka!l'ka!p'ka#R'ka#o'ka#p'ka#q'ka#r'ka#s'ka#t'ka#u'ka#v'ka#w'ka#y'ka#{'ka#|'ka$P'ka(b'ka(s'ka(z'ka!]'ka!^'ka~O!Q*PO'z*QOP'maR'ma['maj'man'mar'ma!S'ma!l'ma!p'ma#R'ma#o'ma#p'ma#q'ma#r'ma#s'ma#t'ma#u'ma#v'ma#w'ma#y'ma#{'ma#|'ma$P'ma(b'ma(s'ma(z'ma({'ma!]'ma!^'ma~OP%PqR%Pq[%Pqj%Pqr%Pq!S%Pq!l%Pq!p%Pq#R%Pq#o%Pq#p%Pq#q%Pq#r%Pq#s%Pq#t%Pq#u%Pq#v%Pq#w%Pq#y%Pq#{%Pq#|%Pq$P%Pq(b%Pq(s%Pq!]%Pq!^%Pq~P'&xOg%f!Z!]%f!Z#`%f!Z$P%f!Z~P!1WO!Y;hO~P#/eOr;iO!g#vO(s'qO~O[;kO!Y;hO~P#/eO!]'qq!^'qq~P#BiO!]#h!Z!^#h!Z~P#BiO#l%f!ZP%f!ZR%f!Z[%f!Za%f!Zj%f!Zr%f!Z!S%f!Z!]%f!Z!l%f!Z!p%f!Z#R%f!Z#o%f!Z#p%f!Z#q%f!Z#r%f!Z#s%f!Z#t%f!Z#u%f!Z#v%f!Z#w%f!Z#y%f!Z#{%f!Z#|%f!Z'{%f!Z(b%f!Z(s%f!Z!k%f!Z!Y%f!Z'x%f!Z#`%f!Zv%f!Z!_%f!Z%j%f!Z!g%f!Z~P#/eOr;tO!g#vO(s'qO~O!Y;uO~P#/eOr;|O!g#vO(s'qO~O!Y;}O~P#/eOP%f!ZR%f!Z[%f!Zj%f!Zr%f!Z!S%f!Z!l%f!Z!p%f!Z#R%f!Z#o%f!Z#p%f!Z#q%f!Z#r%f!Z#s%f!Z#t%f!Z#u%f!Z#v%f!Z#w%f!Z#y%f!Z#{%f!Z#|%f!Z$P%f!Z(b%f!Z(s%f!Z!]%f!Z!^%f!Z~P'&xOr<QO!g#vO(s'qO~Ov(gX~P1qO!Q%rO~P!)[O(V!lO~P!)[O!YfX!]fX#`fX~P%1{OP]XR]X[]Xj]Xr]X!Q]X!S]X!]]X!]fX!l]X!p]X#R]X#S]X#`]X#`fX#lfX#o]X#p]X#q]X#r]X#s]X#t]X#u]X#v]X#w]X#y]X#{]X#|]X$R]X(b]X(s]X(z]X({]X~O!gfX!k]X!kfX(sfX~P'KvOP<UOQ<UOSfOd>QOe!iOpkOr<UOskOtkOzkO|<UO!O<UO!SWO!WkO!XkO!_XO!i<XO!lZO!o<UO!p<UO!q<UO!s<YO!u<]O!x!hO$X!kO$o>OO(U)^O(WTO(ZUO(bVO(p[O~O!]<iO!^$ra~Oh%VOp%WOr%XOs$tOt$tOz%YO|%ZO!O<tO!S${O!_$|O!i>VO!l$xO#k<zO$X%`O$u<vO$w<xO$z%aO(U(wO(WTO(ZUO(b$uO(z$}O({%PO~Ol)eO~P(!lOr!eX(s!eX~P#!nO!^]X!^fX~P'KvO!YfX!Y${X!]fX!]${X#`fX~P!0SO#l<^O~O!g#vO#l<^O~O#`<nO~Oj<bO~O#`<}O!](xX!^(xX~O#`<nO!](vX!^(vX~O#l=OO~Og=QO~P!1WO#l=WO~O#l=XO~Og=QO(U&ZO~O!g#vO#l=YO~O!g#vO#l=OO~O$P=ZO~P#BiO#l=[O~O#l=]O~O#l=bO~O#l=cO~O#l=dO~O#l=eO~O$P=fO~P!1WO$P=gO~P!1WOl=rO~P7eOk#S#T#U#W#X#[#j#k#v$o$u$w$z%^%_%i%j%k%r%t%w%x%z%|~(PT#p!X'}(V#qs#o#rr!Q(O$^(O(U$`(f~",goto:"$8v)^PPPPPP)_PP)bP)sP+X/]PPPP6jPP7QPP<|PPP@pPAYPAYPPPAYPCbPAYPAYPAYPCfPCkPDYPIQPPPIUPPPPIULWPPPL^MOPIUPIUPP! ^IUPPPIUPIUP!#dIUP!&y!(O!(XP!({!)P!({!,]PPPPPPP!,|!(OPP!-^!/OP!2_IUIU!2d!5o!:[!:[!>Y!>bPPP!>hIUPPPPPPPPP!AvP!CSPPIU!DdPIUPIUIUIUIUIUPIU!EuP!IOP!LTP!LX!Lc!Lg!LgP!H{P!Lk!LkP# pP# tIUPIU# z#%OCfAYPAYPAYAYP#&]AYAY#(oAY#+gAY#-sAYAY#.c#0w#0w#0|#1V#0w#1bPP#0wPAY#1zAY#5yAYAY6jPPP#:OPPP#:i#:iP#:iP#;P#:iPP#;VP#:|P#:|#;j#:|#<U#<[#<_)bP#<b)bP#<k#<k#<kP)bP)bP)bP)bPP)bP#<q#<tP#<t)bP#<xP#<{P)bP)bP)bP)bP)bP)b)bPP#=R#=X#=d#=j#=p#=v#=|#>[#>b#>l#>r#>|#?S#?d#?j#@[#@n#@t#@z#AY#Ao#Cd#Cr#Cy#Ee#Es#Ge#Gs#Gy#HP#HV#Ha#Hg#Hm#Hw#IZ#IaPPPPPPPPPPP#IgPPPPPPP#J[#Mi$ R$ Y$ bPPP$&|P$'V$*O$0f$0i$0l$1k$1n$1u$1}P$2T$2WP$2t$2x$3p$5O$5T$5kPP$5p$5v$5z$5}$6R$6V$7R$7j$8R$8V$8Y$8]$8g$8j$8n$8rR!|RoqOXst!Z#d%m&r&t&u&w,u,z2]2`Y!vQ'a-f1p5{Q%tvQ%|yQ&T|Q&j!VS'W!e-^Q'g!iS'm!r!yU*l$|*[*pQ+q%}S,O&V&WQ,f&dQ-d'`Q-n'hQ-v'nQ0]*rQ1c,QQ1z,gR<{<Y%SdOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_,r,u,z-j-r.Q.W.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3l4z6T6e6f6i6|8t9T9_S#q]<V!p)`$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RU+Q%]<s<tQ+v&PQ,h&gQ,o&oQ0y+iQ1O+kQ1Z+wQ2S,mQ3`.hQ5`0}Q5f1UQ6[1{Q7Y3dQ8`5gR9e7['OkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>R!S!nQ!r!v!y!z$|'W'`'a'm'n'o*l*p*r*s-^-d-f-v0]0`1p5{5}%[$ti#v$b$c$d$x${%O%Q%^%_%c)z*S*U*W*Z*b*h*x*y+h+k,U,X.g/Q/e/n/y/z/|0a0c0j0k0p1g1j1r3c4^4_4j4o5Q5[5_6S7W7v8Q8V8[8q9b9p9y:P:`:r;Q;[;d;k<l<m<o<p<q<r<u<v<w<x<y<z=R=S=T=U=W=X=[=]=^=_=`=a=b=c=f=g>O>W>X>[>]Q&X|S'U!e*[S']%i-aQ+v&PQ,R&WQ,h&gQ0o+TQ1Z+wQ1`+}Q2R,lQ2S,mQ5f1UQ5o1bQ6[1{Q6_1}Q6`2QQ8`5gQ8c5lQ8|6bQ:X8dQ:f8yR;V:YrnOXst!V!Z#d%m&i&r&t&u&w,u,z2]2`R,j&k&x^OPXYstuvwz!Z!`!g!j!o#S#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'c's(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>Q>R[#]WZ#W#Z'X(U!b%jm#h#i#l$x%e%h(_(i(j(k*Z*_*c+[+^+`,q-W.U.[.].^.`/n/q2e3[3]4a6r7TQ%wxQ%{yW&Q|&V&W,QQ&_!TQ'd!hQ'f!iQ(r#sS+p%|%}Q+t&PQ,a&bQ,e&dS-m'g'hQ.j(sQ1S+qQ1Y+wQ1[+xQ1_+|Q1u,bS1y,f,gQ2|-nQ5e1UQ5i1XQ5n1aQ6Z1zQ8_5gQ8b5kQ8f5pQ:T8^R;T:U!U$zi$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>X!^%yy!i!u%{%|%}'V'f'g'h'l'v*k+p+q-Z-m-n-u0S0V1S2u2|3T4r4s4v7}9{Q+j%wQ,V&[Q,Y&]Q,d&dQ.i(rQ1t,aU1x,e,f,gQ3e.jQ6U1uS6Y1y1zQ8x6Z#f>S#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]o>T<l<m<p<r<v<x<z=S=U=X=]=_=a=c=gW%Ti%V*z>OS&[!Q&iQ&]!RQ&^!SU+O%[%d=rR,T&Y%]%Si#v$b$c$d$x${%O%Q%^%_%c)z*S*U*W*Z*b*h*x*y+h+k,U,X.g/Q/e/n/y/z/|0a0c0j0k0p1g1j1r3c4^4_4j4o5Q5[5_6S7W7v8Q8V8[8q9b9p9y:P:`:r;Q;[;d;k<l<m<o<p<q<r<u<v<w<x<y<z=R=S=T=U=W=X=[=]=^=_=`=a=b=c=f=g>O>W>X>[>]T){$u)|V+Q%]<s<tW']!e%i*[-aS)O#y#zQ+e%rQ+{&SS.c(n(oQ1k,ZQ5T0lR8i5u'OkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>R$g$^c#Y#e%q%s%u(T(Z(u(z)S)T)U)V)W)X)Y)Z)[)])_)a)c)h)r+f+z-[-y.O.T.V.t.w.{.}/O/P/c0q2n3O3V3k3p3q3r3s3t3u3v3w3x3y3z3{3|4P4Q4X5X5c6u6{7Q7a7b7k7l8k9X9]9g9m9n:o;W;`<W=uT#TV#U'PkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RQ'Y!eR2q-^!W!nQ!e!r!v!y!z$|'W'`'a'm'n'o*[*l*p*r*s-^-d-f-v0]0`1p5{5}R1m,]nqOXst!Z#d%m&r&t&u&w,u,z2]2`Q&y!^Q'w!xS(t#u<^Q+n%zQ,_&_Q,`&aQ-k'eQ-x'pS.s(y=OS0r+Y=YQ1Q+oQ1o,^Q2d,|Q2f,}Q2m-XQ2z-lQ2}-pS5Y0s=dQ5a1RS5d1T=eQ6t2oQ6x2{Q6}3SQ8]5bQ9Y6vQ9Z6yQ9^7OR:l9V$b$]c#Y#e%s%u(T(Z(u(z)S)T)U)V)W)X)Y)Z)[)])_)a)c)h)r+f+z-[-y.O.T.V.t.w.{/O/P/c0q2n3O3V3k3p3q3r3s3t3u3v3w3x3y3z3{3|4P4Q4X5X5c6u6{7Q7a7b7k7l8k9X9]9g9m9n:o;W;`<W=uS(p#p'jQ)Q#zS+d%q.}S.d(o(qR3^.e'OkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RS#q]<VQ&t!XQ&u!YQ&w![Q&x!]R2[,xQ'b!hQ+g%wQ-i'dS.f(r+jQ2x-hW3b.i.j0x0zQ6w2yW7U3_3a3e5^U9a7V7X7ZU:q9c9d9fS;b:p:sQ;p;cR;x;qU!wQ'a-fT5y1p5{!Q_OXZ`st!V!Z#d#h%e%m&i&k&r&t&u&w(k,u,z.]2]2`]!pQ!r'a-f1p5{T#q]<V%^{OPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_+i,r,u,z-j-r.Q.W.h.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3d3l4z6T6e6f6i6|7[8t9T9_S)O#y#zS.c(n(o!q=k$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RU$fd)`,oS(q#p'jU*w%R(x4OU0n+P.o7gQ5^0yQ7V3`Q9d7YR:s9em!tQ!r!v!y!z'a'm'n'o-f-v1p5{5}Q'u!uS(g#g2VS-t'l'xQ/t*^Q0S*kQ3U-wQ4f/uQ4r0UQ4s0VQ4x0_Q7r4`S7}4t4vS8R4y4{Q9r7sQ9v7yQ9{8OQ:Q8TS:{9w9xS;g:|;PS;s;h;iS;{;t;uS<P;|;}R<S<QQ#wbQ't!uS(f#g2VS(h#m+XQ+Z%fQ+l%xQ+r&OU-s'l'u'xQ.X(gU/s*^*a/xQ0T*kQ0W*mQ1P+mQ1v,cS3R-t-wQ3Z.aS4e/t/uQ4n0QS4q0S0_Q4u0XQ6W1wQ7P3US7q4`4bQ7u4fU7|4r4x4{Q8P4wQ8v6XS9q7r7sQ9u7yQ9}8RQ:O8SQ:c8wQ:y9rS:z9v9xQ;S:QQ;^:dS;f:{;PS;r;g;hS;z;s;uS<O;{;}Q<R<PQ<T<SQ=n=iQ=z=sR={=tV!wQ'a-f%^aOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_+i,r,u,z-j-r.Q.W.h.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3d3l4z6T6e6f6i6|7[8t9T9_S#wz!j!p=h$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RR=n>Q%^bOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_+i,r,u,z-j-r.Q.W.h.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3d3l4z6T6e6f6i6|7[8t9T9_Q%fj!^%xy!i!u%{%|%}'V'f'g'h'l'v*k+p+q-Z-m-n-u0S0V1S2u2|3T4r4s4v7}9{S&Oz!jQ+m%yQ,c&dW1w,d,e,f,gU6X1x1y1zS8w6Y6ZQ:d8x!p=i$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RQ=s>PR=t>Q%QeOPXYstuvw!Z!`!g!o#S#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&r&t&u&w&{'T'c's(W(^(e(y({)P*O*j+Y+_+i,r,u,z-j-r.Q.W.h.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3d3l4z6T6e6f6i6|7[8t9T9_Y#bWZ#W#Z(U!b%jm#h#i#l$x%e%h(_(i(j(k*Z*_*c+[+^+`,q-W.U.[.].^.`/n/q2e3[3]4a6r7TQ,p&o!n=j$Z$n)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RR=m'XU'^!e%i*[R2s-aX'[!e%i*[-a%SdOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_,r,u,z-j-r.Q.W.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3l4z6T6e6f6i6|8t9T9_!p)`$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RQ,o&oQ0y+iQ3`.hQ7Y3dR9e7[!b$Tc#Y%q(T(Z(u(z)[)])a)h+z-y.O.T.V.t.w/c0q3O3V3k3{5X5c6{7Q7a9]:o<W}<d)_)r-[.}2n3p3y3z4P4X6u7b7k7l8k9X9g9m9n;W;`=u!f$Vc#Y%q(T(Z(u(z)X)Y)[)])a)h+z-y.O.T.V.t.w/c0q3O3V3k3{5X5c6{7Q7a9]:o<W!R<f)_)r-[.}2n3p3v3w3y3z4P4X6u7b7k7l8k9X9g9m9n;W;`=u!^$Zc#Y%q(T(Z(u(z)a)h+z-y.O.T.V.t.w/c0q3O3V3k3{5X5c6{7Q7a9]:o<WQ4_/lx>R)_)r-[.}2n3p4P4X6u7b7k7l8k9X9g9m9n;W;`=uQ>W>YR>X>Z'OkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RS$oh$pR4U/V'VgOPWXYZhstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n$p%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/V/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RT$kf$qQ$ifS)k$l)oR)w$qT$jf$qT)m$l)o'VhOPWXYZhstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n$p%m%t&R&k&n&o&r&t&u&w&{'T'X'c's(U(W(^(e(y({)P)t*O*j+Y+_+i,r,u,z-Y-j-r.Q.W.h.u.|/V/W/o0^0m0s1T1s2T2U2W2Y2]2`2b2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>RT$oh$pQ$rhR)v$p%^jOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'c's(U(W(^(e(y({)P*O*j+Y+_+i,r,u,z-j-r.Q.W.h.u.|/o0^0m0s1T1s2T2U2W2Y2]2`2b3Q3W3d3l4z6T6e6f6i6|7[8t9T9_!q>P$Z$n'X)t-Y/W2p4T5w6s:Z:m<U<X<Y<]<^<_<`<a<b<c<d<e<f<g<h<i<k<n<{<}=O=Q=Y=Z=d=e>R#glOPXZst!Z!`!o#S#d#o#{$n%m&k&n&o&r&t&u&w&{'T'c)P)t*j+_+i,r,u,z-j.h/W/o0^0m1s2T2U2W2Y2]2`2b3d4T4z6T6e6f6i7[8t9T!U%Ri$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>X#f(x#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]Q+U%aQ/d*Po4O<l<m<p<r<v<x<z=S=U=X=]=_=a=c=g!U$yi$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>XQ*d$zU*m$|*[*pQ+V%bQ0X*n#f=p#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]n=q<l<m<p<r<v<x<z=S=U=X=]=_=a=c=gQ=v>SQ=w>TQ=x>UR=y>V!U%Ri$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>X#f(x#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]o4O<l<m<p<r<v<x<z=S=U=X=]=_=a=c=gnoOXst!Z#d%m&r&t&u&w,u,z2]2`S*g${*ZQ-T'OQ-U'QR4i/z%[%Si#v$b$c$d$x${%O%Q%^%_%c)z*S*U*W*Z*b*h*x*y+h+k,U,X.g/Q/e/n/y/z/|0a0c0j0k0p1g1j1r3c4^4_4j4o5Q5[5_6S7W7v8Q8V8[8q9b9p9y:P:`:r;Q;[;d;k<l<m<o<p<q<r<u<v<w<x<y<z=R=S=T=U=W=X=[=]=^=_=`=a=b=c=f=g>O>W>X>[>]Q,W&]Q1i,YQ5s1hR8h5tV*o$|*[*pU*o$|*[*pT5z1p5{S0Q*j/oQ4w0^T8S4z:]Q+l%xQ0W*mQ1P+mQ1v,cQ6W1wQ8v6XQ:c8wR;^:d!U%Oi$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>Xx*S$v)f*T*v+W/w0e0f4R4g5R5S5W7p8U:R:x=o=|=}S0a*u0b#f<o#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]n<p<l<m<p<r<v<x<z=S=U=X=]=_=a=c=g!d=R(v)d*]*f.k.n.r/`/l/}0w1f3h4[4h4l5r7]7`7w7z8X8Z9t9|:S:};R;e;j;v>Y>Z`=S3}7c7f7j9h:t:w;yS=^.m3iT=_7e9k!U%Qi$d%O%Q%^%_%c*S*U*b*x*y/Q/y0a0c0j0k0p4_5Q8V9p>O>W>X|*U$v)f*V*u+W/h/w0e0f4R4g4|5R5S5W7p8U:R:x=o=|=}S0c*v0d#f<q#v$b$c$x${)z*W*Z*h+h+k,U,X.g/e/n/z/|1g1j1r3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k<o<q<u<w<y=R=T=W=[=^=`=b=f>[>]n<r<l<m<p<r<v<x<z=S=U=X=]=_=a=c=g!h=T(v)d*]*f.l.m.r/`/l/}0w1f3f3h4[4h4l5r7]7^7`7w7z8X8Z9t9|:S:};R;e;j;v>Y>Zd=U3}7d7e7j9h9i:t:u:w;yS=`.n3jT=a7f9lrnOXst!V!Z#d%m&i&r&t&u&w,u,z2]2`Q&f!UR,r&ornOXst!V!Z#d%m&i&r&t&u&w,u,z2]2`R&f!UQ,[&^R1e,TsnOXst!V!Z#d%m&i&r&t&u&w,u,z2]2`Q1q,aS6R1t1uU8p6P6Q6US:_8r8sS;Y:^:aQ;m;ZR;w;nQ&m!VR,k&iR6_1}R:f8yW&Q|&V&W,QR1[+xQ&r!WR,u&sR,{&xT2^,z2`R-P&yQ-O&yR2g-PQ'z!{R-z'zSsOtQ#dXT%ps#dQ#OTR'|#OQ#RUR(O#RQ)|$uR/a)|Q#UVR(R#UQ#XWU(X#X(Y.RQ(Y#YR.R(ZQ-_'YR2r-_Q.v(zS3m.v3nR3n.wQ-f'aR2v-fY!rQ'a-f1p5{R'k!rQ/R)fR4S/RU#_W%h*ZU(`#_(a.SQ(a#`R.S([Q-b'^R2t-bt`OXst!V!Z#d%m&i&k&r&t&u&w,u,z2]2`S#hZ%eU#r`#h.]R.](kQ(l#jQ.Y(hW.b(l.Y3X7RQ3X.ZR7R3YQ)o$lR/X)oQ$phR)u$pQ$`cU)b$`-}<jQ-}<WR<j)rQ/r*^W4c/r4d7t9sU4d/s/t/uS7t4e4fR9s7u$e*R$v(v)d)f*]*f*u*v+R+S+W.m.n.p.q.r/`/h/j/l/w/}0e0f0w1f3f3g3h3}4R4[4g4h4l4|5O5R5S5W5r7]7^7_7`7e7f7h7i7j7p7w7z8U8X8Z9h9i9j9t9|:R:S:t:u:v:w:x:};R;e;j;v;y=o=|=}>Y>ZQ/{*fU4k/{4m7xQ4m/}R7x4lS*p$|*[R0Z*px*T$v)f*u*v+W/w0e0f4R4g5R5S5W7p8U:R:x=o=|=}!d.k(v)d*]*f.m.n.r/`/l/}0w1f3h4[4h4l5r7]7`7w7z8X8Z9t9|:S:};R;e;j;v>Y>ZU/i*T.k7ca7c3}7e7f7j9h:t:w;yQ0b*uQ3i.mU4}0b3i9kR9k7e|*V$v)f*u*v+W/h/w0e0f4R4g4|5R5S5W7p8U:R:x=o=|=}!h.l(v)d*]*f.m.n.r/`/l/}0w1f3f3h4[4h4l5r7]7^7`7w7z8X8Z9t9|:S:};R;e;j;v>Y>ZU/k*V.l7de7d3}7e7f7j9h9i:t:u:w;yQ0d*vQ3j.nU5P0d3j9lR9l7fQ*{%UR0h*{Q5]0wR8Y5]Q+a%kR0v+aQ5v1kS8j5v:[R:[8kQ,^&_R1n,^Q5{1pR8m5{Q1|,hS6]1|8zR8z6_Q1V+tW5h1V5j8a:VQ5j1YQ8a5iR:V8bQ+y&QR1]+yQ2`,zR6m2`YrOXst#dQ&v!ZQ+c%mQ,t&rQ,v&tQ,w&uQ,y&wQ2Z,uS2^,z2`R6l2]Q%opQ&z!_Q&}!aQ'P!bQ'R!cQ'r!uQ+b%lQ+n%zQ,S&XQ,j&mQ-R&|W-q'l't'u'xQ-x'pQ0Y*oQ1Q+oQ1d,RS2P,k,nQ2h-QQ2i-TQ2j-UQ2}-pW3P-s-t-w-yQ5a1RQ5m1`Q5q1fQ6V1vQ6a2RQ6k2[U6z3O3R3UQ6}3SQ8]5bQ8e5oQ8g5rQ8l5zQ8u6WQ8{6`S9[6{7PQ9^7OQ:W8cQ:b8vQ:g8|Q:n9]Q;U:XQ;]:cQ;a:oQ;l;VR;o;^Q%zyQ'e!iQ'p!uU+o%{%|%}Q-X'VU-l'f'g'hS-p'l'vQ0R*kS1R+p+qQ2o-ZS2{-m-nQ3S-uS4p0S0VQ5b1SQ6v2uQ6y2|Q7O3TU7{4r4s4vQ9z7}R;O9{S$wi>OR*|%VU%Ui%V>OR0g*zQ$viS(v#v+kS)d$b$cQ)f$dQ*]$xS*f${*ZQ*u%OQ*v%QQ+R%^Q+S%_Q+W%cQ.m<oQ.n<qQ.p<uQ.q<wQ.r<yQ/`)zQ/h*SQ/j*UQ/l*WQ/w*bS/}*h/nQ0e*xQ0f*yl0w+h,X.g1j1r3c6S7W8q9b:`:r;[;dQ1f,UQ3f=RQ3g=TQ3h=WS3}<l<mQ4R/QS4[/e4^Q4g/yQ4h/zQ4l/|Q4|0aQ5O0cQ5R0jQ5S0kQ5W0pQ5r1gQ7]=[Q7^=^Q7_=`Q7`=bQ7e<pQ7f<rQ7h<vQ7i<xQ7j<zQ7p4_Q7w4jQ7z4oQ8U5QQ8X5[Q8Z5_Q9h=XQ9i=SQ9j=UQ9t7vQ9|8QQ:R8VQ:S8[Q:t=]Q:u=_Q:v=aQ:w=cQ:x9pQ:}9yQ;R:PQ;e=fQ;j;QQ;v;kQ;y=gQ=o>OQ=|>WQ=}>XQ>Y>[R>Z>]Q+P%]Q.o<sR7g<tnpOXst!Z#d%m&r&t&u&w,u,z2]2`Q!fPS#fZ#oQ&|!`W'i!o*j0^4zQ(Q#SQ)R#{Q)s$nS,n&k&nQ,s&oQ-Q&{S-V'T/oQ-h'cQ.y)PQ/])tQ0t+_Q0z+iQ2X,rQ2y-jQ3a.hQ4W/WQ5U0mQ6Q1sQ6c2TQ6d2UQ6h2WQ6j2YQ6o2bQ7Z3dQ7m4TQ8s6TQ9P6eQ9Q6fQ9S6iQ9f7[Q:a8tR:k9T#[cOPXZst!Z!`!o#d#o#{%m&k&n&o&r&t&u&w&{'T'c)P*j+_+i,r,u,z-j.h/o0^0m1s2T2U2W2Y2]2`2b3d4z6T6e6f6i7[8t9TQ#YWQ#eYQ%quQ%svS%uw!gS(T#W(WQ(Z#ZQ(u#uQ(z#xQ)S$OQ)T$PQ)U$QQ)V$RQ)W$SQ)X$TQ)Y$UQ)Z$VQ)[$WQ)]$XQ)_$ZQ)a$_Q)c$aQ)h$eW)r$n)t/W4TQ+f%tQ+z&RS-['X2pQ-y'sS.O(U.QQ.T(^Q.V(eQ.t(yQ.w({Q.{<UQ.}<XQ/O<YQ/P<]Q/c*OQ0q+YQ2n-YQ3O-rQ3V.WQ3k.uQ3p<^Q3q<_Q3r<`Q3s<aQ3t<bQ3u<cQ3v<dQ3w<eQ3x<fQ3y<gQ3z<hQ3{.|Q3|<kQ4P<nQ4Q<{Q4X<iQ5X0sQ5c1TQ6u<}Q6{3QQ7Q3WQ7a3lQ7b=OQ7k=QQ7l=YQ8k5wQ9X6sQ9]6|Q9g=ZQ9m=dQ9n=eQ:o9_Q;W:ZQ;`:mQ<W#SR=u>RR#[WR'Z!el!tQ!r!v!y!z'a'm'n'o-f-v1p5{5}S'V!e-^U*k$|*[*pS-Z'W'`S0V*l*rQ0_*sQ2u-dQ4v0]R4{0`R(|#xQ!fQT-e'a-f]!qQ!r'a-f1p5{Q#p]R'j<VR)g$dY!uQ'a-f1p5{Q'l!rS'v!v!yS'x!z5}S-u'm'nQ-w'oR3T-vT#kZ%eS#jZ%eS%km,qU(h#h#i#lS.Z(i(jQ._(kQ0u+`Q3Y.[U3Z.].^.`S7S3[3]R9`7Td#^W#W#Z%h(U(_*Z+[.U/nr#gZm#h#i#l%e(i(j(k+`.[.].^.`3[3]7TS*^$x*cQ/u*_Q2V,qQ2l-WQ4`/qQ6q2eQ7s4aQ9W6rT=l'X+^V#aW%h*ZU#`W%h*ZS(V#W(_U([#Z+[/nS-]'X+^T.P(U.UV'_!e%i*[Q$lfR)y$qT)n$l)oR4V/VT*`$x*cT*i${*ZQ0x+hQ1h,XQ3_.gQ5t1jQ6P1rQ7X3cQ8r6SQ9c7WQ:^8qQ:p9bQ;Z:`Q;c:rQ;n;[R;q;dnqOXst!Z#d%m&r&t&u&w,u,z2]2`Q&l!VR,j&itmOXst!U!V!Z#d%m&i&r&t&u&w,u,z2]2`R,q&oT%lm,qR1l,ZR,i&gQ&U|S,P&V&WR1_,QR+u&PT&p!W&sT&q!W&sT2_,z2`",nodeNames:"\u26A0 ArithOp ArithOp ?. JSXStartTag LineComment BlockComment Script Hashbang ExportDeclaration export Star as VariableName String Escape from ; default FunctionDeclaration async function VariableDefinition > < TypeParamList in out const TypeDefinition extends ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType InterpolationEnd Interpolation InterpolationStart NullType null VoidType void TypeofType typeof MemberExpression . PropertyName [ TemplateString Escape Interpolation super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewTarget new NewExpression ) ( ArgList UnaryExpression delete LogicOp BitOp YieldExpression yield AwaitExpression await ParenthesizedExpression ClassExpression class ClassBody MethodDeclaration Decorator @ MemberExpression PrivatePropertyName CallExpression TypeArgList CompareOp < declare Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly accessor Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty VariableDefinition Privacy readonly Arrow MemberExpression BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof satisfies CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression InstantiationExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast < ArrowFunction TypeParamList SequenceExpression InstantiationExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature PropertyDefinition CallSignature TypePredicate asserts is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var using TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration defer ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try CatchClause catch FinallyClause finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement SingleExpression SingleClassItem",maxTerm:381,context:u5,nodeProps:[["isolate",-8,5,6,14,37,39,51,53,55,""],["group",-26,9,17,19,68,208,212,216,217,219,222,225,235,238,244,246,248,250,253,259,265,267,269,271,273,275,276,"Statement",-34,13,14,32,35,36,42,51,54,55,57,62,70,72,76,80,82,84,85,110,111,121,122,137,140,142,143,144,145,146,148,149,168,170,172,"Expression",-23,31,33,37,41,43,45,174,176,178,179,181,182,183,185,186,187,189,190,191,202,204,206,207,"Type",-3,88,103,109,"ClassItem"],["openedBy",23,"<",38,"InterpolationStart",56,"[",60,"{",73,"(",161,"JSXStartCloseTag"],["closedBy",-2,24,169,">",40,"InterpolationEnd",50,"]",61,"}",74,")",166,"JSXEndTag"]],propSources:[b5],skippedNodes:[0,5,6,279],repeatNodeCount:37,tokenData:"$Fq07[R!bOX%ZXY+gYZ-yZ[+g[]%Z]^.c^p%Zpq+gqr/mrs3cst:_tuEruvJSvwLkwx! Yxy!'iyz!(sz{!)}{|!,q|}!.O}!O!,q!O!P!/Y!P!Q!9j!Q!R#:O!R![#<_![!]#I_!]!^#Jk!^!_#Ku!_!`$![!`!a$$v!a!b$*T!b!c$,r!c!}Er!}#O$-|#O#P$/W#P#Q$4o#Q#R$5y#R#SEr#S#T$7W#T#o$8b#o#p$<r#p#q$=h#q#r$>x#r#s$@U#s$f%Z$f$g+g$g#BYEr#BY#BZ$A`#BZ$ISEr$IS$I_$A`$I_$I|Er$I|$I}$Dk$I}$JO$Dk$JO$JTEr$JT$JU$A`$JU$KVEr$KV$KW$A`$KW&FUEr&FU&FV$A`&FV;'SEr;'S;=`I|<%l?HTEr?HT?HU$A`?HUOEr(n%d_$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z&j&hT$j&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c&j&zP;=`<%l&c'|'U]$j&j([!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!b(SU([!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!b(iP;=`<%l'}'|(oP;=`<%l&}'[(y]$j&j(XpOY(rYZ&cZr(rrs&cs!^(r!^!_)r!_#O(r#O#P&c#P#o(r#o#p)r#p;'S(r;'S;=`*a<%lO(rp)wU(XpOY)rZr)rs#O)r#P;'S)r;'S;=`*Z<%lO)rp*^P;=`<%l)r'[*dP;=`<%l(r#S*nX(Xp([!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g#S+^P;=`<%l*g(n+dP;=`<%l%Z07[+rq$j&j(Xp([!b'}0/lOX%ZXY+gYZ&cZ[+g[p%Zpq+gqr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p$f%Z$f$g+g$g#BY%Z#BY#BZ+g#BZ$IS%Z$IS$I_+g$I_$JT%Z$JT$JU+g$JU$KV%Z$KV$KW+g$KW&FU%Z&FU&FV+g&FV;'S%Z;'S;=`+a<%l?HT%Z?HT?HU+g?HUO%Z07[.ST(Y#S$j&j(O0/lO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c07[.n_$j&j(Xp([!b(O0/lOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z)3p/x`$j&j!p),Q(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`0z!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW1V`#w(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`2X!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW2d_#w(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'At3l_(W':f$j&j([!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k(^4r_$j&j([!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k&z5vX$j&jOr5qrs6cs!^5q!^!_6y!_#o5q#o#p6y#p;'S5q;'S;=`7h<%lO5q&z6jT$e`$j&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c`6|TOr6yrs7]s;'S6y;'S;=`7b<%lO6y`7bO$e``7eP;=`<%l6y&z7kP;=`<%l5q(^7w]$e`$j&j([!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!r8uZ([!bOY8pYZ6yZr8prs9hsw8pwx6yx#O8p#O#P6y#P;'S8p;'S;=`:R<%lO8p!r9oU$e`([!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!r:UP;=`<%l8p(^:[P;=`<%l4k%9[:hh$j&j(Xp([!bOY%ZYZ&cZq%Zqr<Srs&}st%ZtuCruw%Zwx(rx!^%Z!^!_*g!_!c%Z!c!}Cr!}#O%Z#O#P&c#P#R%Z#R#SCr#S#T%Z#T#oCr#o#p*g#p$g%Z$g;'SCr;'S;=`El<%lOCr(r<__WS$j&j(Xp([!bOY<SYZ&cZr<Srs=^sw<Swx@nx!^<S!^!_Bm!_#O<S#O#P>`#P#o<S#o#pBm#p;'S<S;'S;=`Cl<%lO<S(Q=g]WS$j&j([!bOY=^YZ&cZw=^wx>`x!^=^!^!_?q!_#O=^#O#P>`#P#o=^#o#p?q#p;'S=^;'S;=`@h<%lO=^&n>gXWS$j&jOY>`YZ&cZ!^>`!^!_?S!_#o>`#o#p?S#p;'S>`;'S;=`?k<%lO>`S?XSWSOY?SZ;'S?S;'S;=`?e<%lO?SS?hP;=`<%l?S&n?nP;=`<%l>`!f?xWWS([!bOY?qZw?qwx?Sx#O?q#O#P?S#P;'S?q;'S;=`@b<%lO?q!f@eP;=`<%l?q(Q@kP;=`<%l=^'`@w]WS$j&j(XpOY@nYZ&cZr@nrs>`s!^@n!^!_Ap!_#O@n#O#P>`#P#o@n#o#pAp#p;'S@n;'S;=`Bg<%lO@ntAwWWS(XpOYApZrAprs?Ss#OAp#O#P?S#P;'SAp;'S;=`Ba<%lOAptBdP;=`<%lAp'`BjP;=`<%l@n#WBvYWS(Xp([!bOYBmZrBmrs?qswBmwxApx#OBm#O#P?S#P;'SBm;'S;=`Cf<%lOBm#WCiP;=`<%lBm(rCoP;=`<%l<S%9[C}i$j&j(p%1l(Xp([!bOY%ZYZ&cZr%Zrs&}st%ZtuCruw%Zwx(rx!Q%Z!Q![Cr![!^%Z!^!_*g!_!c%Z!c!}Cr!}#O%Z#O#P&c#P#R%Z#R#SCr#S#T%Z#T#oCr#o#p*g#p$g%Z$g;'SCr;'S;=`El<%lOCr%9[EoP;=`<%lCr07[FRk$j&j(Xp([!b$^#t(U,2j(f$I[OY%ZYZ&cZr%Zrs&}st%ZtuEruw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Er![!^%Z!^!_*g!_!c%Z!c!}Er!}#O%Z#O#P&c#P#R%Z#R#SEr#S#T%Z#T#oEr#o#p*g#p$g%Z$g;'SEr;'S;=`I|<%lOEr+dHRk$j&j(Xp([!b$^#tOY%ZYZ&cZr%Zrs&}st%ZtuGvuw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Gv![!^%Z!^!_*g!_!c%Z!c!}Gv!}#O%Z#O#P&c#P#R%Z#R#SGv#S#T%Z#T#oGv#o#p*g#p$g%Z$g;'SGv;'S;=`Iv<%lOGv+dIyP;=`<%lGv07[JPP;=`<%lEr(KWJ_`$j&j(Xp([!b#q(ChOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KWKl_$j&j$R(Ch(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z,#xLva({+JY$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sv%ZvwM{wx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KWNW`$j&j#{(Ch(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'At! c_(Z';W$j&j(XpOY!!bYZ!#hZr!!brs!#hsw!!bwx!$xx!^!!b!^!_!%z!_#O!!b#O#P!#h#P#o!!b#o#p!%z#p;'S!!b;'S;=`!'c<%lO!!b'l!!i_$j&j(XpOY!!bYZ!#hZr!!brs!#hsw!!bwx!$xx!^!!b!^!_!%z!_#O!!b#O#P!#h#P#o!!b#o#p!%z#p;'S!!b;'S;=`!'c<%lO!!b&z!#mX$j&jOw!#hwx6cx!^!#h!^!_!$Y!_#o!#h#o#p!$Y#p;'S!#h;'S;=`!$r<%lO!#h`!$]TOw!$Ywx7]x;'S!$Y;'S;=`!$l<%lO!$Y`!$oP;=`<%l!$Y&z!$uP;=`<%l!#h'l!%R]$e`$j&j(XpOY(rYZ&cZr(rrs&cs!^(r!^!_)r!_#O(r#O#P&c#P#o(r#o#p)r#p;'S(r;'S;=`*a<%lO(r!Q!&PZ(XpOY!%zYZ!$YZr!%zrs!$Ysw!%zwx!&rx#O!%z#O#P!$Y#P;'S!%z;'S;=`!']<%lO!%z!Q!&yU$e`(XpOY)rZr)rs#O)r#P;'S)r;'S;=`*Z<%lO)r!Q!'`P;=`<%l!%z'l!'fP;=`<%l!!b/5|!'t_!l/.^$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z#&U!)O_!k!Lf$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z-!n!*[b$j&j(Xp([!b(V%&f#r(ChOY%ZYZ&cZr%Zrs&}sw%Zwx(rxz%Zz{!+d{!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW!+o`$j&j(Xp([!b#o(ChOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z+;x!,|`$j&j(Xp([!br+4YOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z,$U!.Z_!]+Jf$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z07[!/ec$j&j(Xp([!b!Q.2^OY%ZYZ&cZr%Zrs&}sw%Zwx(rx!O%Z!O!P!0p!P!Q%Z!Q![!3Y![!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z#%|!0ya$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!O%Z!O!P!2O!P!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z#%|!2Z_![!L^$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad!3eg$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![!3Y![!^%Z!^!_*g!_!g%Z!g!h!4|!h#O%Z#O#P&c#P#R%Z#R#S!3Y#S#X%Z#X#Y!4|#Y#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad!5Vg$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx{%Z{|!6n|}%Z}!O!6n!O!Q%Z!Q![!8S![!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S!8S#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad!6wc$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![!8S![!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S!8S#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad!8_c$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![!8S![!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S!8S#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z07[!9uf$j&j(Xp([!b#p(ChOY!;ZYZ&cZr!;Zrs!<nsw!;Zwx!Lcxz!;Zz{#-}{!P!;Z!P!Q#/d!Q!^!;Z!^!_#(i!_!`#7S!`!a#8i!a!}!;Z!}#O#,f#O#P!Dy#P#o!;Z#o#p#(i#p;'S!;Z;'S;=`#-w<%lO!;Z?O!;fb$j&j(Xp([!b!X7`OY!;ZYZ&cZr!;Zrs!<nsw!;Zwx!Lcx!P!;Z!P!Q#&`!Q!^!;Z!^!_#(i!_!}!;Z!}#O#,f#O#P!Dy#P#o!;Z#o#p#(i#p;'S!;Z;'S;=`#-w<%lO!;Z>^!<w`$j&j([!b!X7`OY!<nYZ&cZw!<nwx!=yx!P!<n!P!Q!Eq!Q!^!<n!^!_!Gr!_!}!<n!}#O!KS#O#P!Dy#P#o!<n#o#p!Gr#p;'S!<n;'S;=`!L]<%lO!<n<z!>Q^$j&j!X7`OY!=yYZ&cZ!P!=y!P!Q!>|!Q!^!=y!^!_!@c!_!}!=y!}#O!CW#O#P!Dy#P#o!=y#o#p!@c#p;'S!=y;'S;=`!Ek<%lO!=y<z!?Td$j&j!X7`O!^&c!_#W&c#W#X!>|#X#Z&c#Z#[!>|#[#]&c#]#^!>|#^#a&c#a#b!>|#b#g&c#g#h!>|#h#i&c#i#j!>|#j#k!>|#k#m&c#m#n!>|#n#o&c#p;'S&c;'S;=`&w<%lO&c7`!@hX!X7`OY!@cZ!P!@c!P!Q!AT!Q!}!@c!}#O!Ar#O#P!Bq#P;'S!@c;'S;=`!CQ<%lO!@c7`!AYW!X7`#W#X!AT#Z#[!AT#]#^!AT#a#b!AT#g#h!AT#i#j!AT#j#k!AT#m#n!AT7`!AuVOY!ArZ#O!Ar#O#P!B[#P#Q!@c#Q;'S!Ar;'S;=`!Bk<%lO!Ar7`!B_SOY!ArZ;'S!Ar;'S;=`!Bk<%lO!Ar7`!BnP;=`<%l!Ar7`!BtSOY!@cZ;'S!@c;'S;=`!CQ<%lO!@c7`!CTP;=`<%l!@c<z!C][$j&jOY!CWYZ&cZ!^!CW!^!_!Ar!_#O!CW#O#P!DR#P#Q!=y#Q#o!CW#o#p!Ar#p;'S!CW;'S;=`!Ds<%lO!CW<z!DWX$j&jOY!CWYZ&cZ!^!CW!^!_!Ar!_#o!CW#o#p!Ar#p;'S!CW;'S;=`!Ds<%lO!CW<z!DvP;=`<%l!CW<z!EOX$j&jOY!=yYZ&cZ!^!=y!^!_!@c!_#o!=y#o#p!@c#p;'S!=y;'S;=`!Ek<%lO!=y<z!EnP;=`<%l!=y>^!Ezl$j&j([!b!X7`OY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#W&}#W#X!Eq#X#Z&}#Z#[!Eq#[#]&}#]#^!Eq#^#a&}#a#b!Eq#b#g&}#g#h!Eq#h#i&}#i#j!Eq#j#k!Eq#k#m&}#m#n!Eq#n#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}8r!GyZ([!b!X7`OY!GrZw!Grwx!@cx!P!Gr!P!Q!Hl!Q!}!Gr!}#O!JU#O#P!Bq#P;'S!Gr;'S;=`!J|<%lO!Gr8r!Hse([!b!X7`OY'}Zw'}x#O'}#P#W'}#W#X!Hl#X#Z'}#Z#[!Hl#[#]'}#]#^!Hl#^#a'}#a#b!Hl#b#g'}#g#h!Hl#h#i'}#i#j!Hl#j#k!Hl#k#m'}#m#n!Hl#n;'S'};'S;=`(f<%lO'}8r!JZX([!bOY!JUZw!JUwx!Arx#O!JU#O#P!B[#P#Q!Gr#Q;'S!JU;'S;=`!Jv<%lO!JU8r!JyP;=`<%l!JU8r!KPP;=`<%l!Gr>^!KZ^$j&j([!bOY!KSYZ&cZw!KSwx!CWx!^!KS!^!_!JU!_#O!KS#O#P!DR#P#Q!<n#Q#o!KS#o#p!JU#p;'S!KS;'S;=`!LV<%lO!KS>^!LYP;=`<%l!KS>^!L`P;=`<%l!<n=l!Ll`$j&j(Xp!X7`OY!LcYZ&cZr!Lcrs!=ys!P!Lc!P!Q!Mn!Q!^!Lc!^!_# o!_!}!Lc!}#O#%P#O#P!Dy#P#o!Lc#o#p# o#p;'S!Lc;'S;=`#&Y<%lO!Lc=l!Mwl$j&j(Xp!X7`OY(rYZ&cZr(rrs&cs!^(r!^!_)r!_#O(r#O#P&c#P#W(r#W#X!Mn#X#Z(r#Z#[!Mn#[#](r#]#^!Mn#^#a(r#a#b!Mn#b#g(r#g#h!Mn#h#i(r#i#j!Mn#j#k!Mn#k#m(r#m#n!Mn#n#o(r#o#p)r#p;'S(r;'S;=`*a<%lO(r8Q# vZ(Xp!X7`OY# oZr# ors!@cs!P# o!P!Q#!i!Q!}# o!}#O#$R#O#P!Bq#P;'S# o;'S;=`#$y<%lO# o8Q#!pe(Xp!X7`OY)rZr)rs#O)r#P#W)r#W#X#!i#X#Z)r#Z#[#!i#[#])r#]#^#!i#^#a)r#a#b#!i#b#g)r#g#h#!i#h#i)r#i#j#!i#j#k#!i#k#m)r#m#n#!i#n;'S)r;'S;=`*Z<%lO)r8Q#$WX(XpOY#$RZr#$Rrs!Ars#O#$R#O#P!B[#P#Q# o#Q;'S#$R;'S;=`#$s<%lO#$R8Q#$vP;=`<%l#$R8Q#$|P;=`<%l# o=l#%W^$j&j(XpOY#%PYZ&cZr#%Prs!CWs!^#%P!^!_#$R!_#O#%P#O#P!DR#P#Q!Lc#Q#o#%P#o#p#$R#p;'S#%P;'S;=`#&S<%lO#%P=l#&VP;=`<%l#%P=l#&]P;=`<%l!Lc?O#&kn$j&j(Xp([!b!X7`OY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#W%Z#W#X#&`#X#Z%Z#Z#[#&`#[#]%Z#]#^#&`#^#a%Z#a#b#&`#b#g%Z#g#h#&`#h#i%Z#i#j#&`#j#k#&`#k#m%Z#m#n#&`#n#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z9d#(r](Xp([!b!X7`OY#(iZr#(irs!Grsw#(iwx# ox!P#(i!P!Q#)k!Q!}#(i!}#O#+`#O#P!Bq#P;'S#(i;'S;=`#,`<%lO#(i9d#)th(Xp([!b!X7`OY*gZr*grs'}sw*gwx)rx#O*g#P#W*g#W#X#)k#X#Z*g#Z#[#)k#[#]*g#]#^#)k#^#a*g#a#b#)k#b#g*g#g#h#)k#h#i*g#i#j#)k#j#k#)k#k#m*g#m#n#)k#n;'S*g;'S;=`+Z<%lO*g9d#+gZ(Xp([!bOY#+`Zr#+`rs!JUsw#+`wx#$Rx#O#+`#O#P!B[#P#Q#(i#Q;'S#+`;'S;=`#,Y<%lO#+`9d#,]P;=`<%l#+`9d#,cP;=`<%l#(i?O#,o`$j&j(Xp([!bOY#,fYZ&cZr#,frs!KSsw#,fwx#%Px!^#,f!^!_#+`!_#O#,f#O#P!DR#P#Q!;Z#Q#o#,f#o#p#+`#p;'S#,f;'S;=`#-q<%lO#,f?O#-tP;=`<%l#,f?O#-zP;=`<%l!;Z07[#.[b$j&j(Xp([!b(P0/l!X7`OY!;ZYZ&cZr!;Zrs!<nsw!;Zwx!Lcx!P!;Z!P!Q#&`!Q!^!;Z!^!_#(i!_!}!;Z!}#O#,f#O#P!Dy#P#o!;Z#o#p#(i#p;'S!;Z;'S;=`#-w<%lO!;Z07[#/o_$j&j(Xp([!bT0/lOY#/dYZ&cZr#/drs#0nsw#/dwx#4Ox!^#/d!^!_#5}!_#O#/d#O#P#1p#P#o#/d#o#p#5}#p;'S#/d;'S;=`#6|<%lO#/d06j#0w]$j&j([!bT0/lOY#0nYZ&cZw#0nwx#1px!^#0n!^!_#3R!_#O#0n#O#P#1p#P#o#0n#o#p#3R#p;'S#0n;'S;=`#3x<%lO#0n05W#1wX$j&jT0/lOY#1pYZ&cZ!^#1p!^!_#2d!_#o#1p#o#p#2d#p;'S#1p;'S;=`#2{<%lO#1p0/l#2iST0/lOY#2dZ;'S#2d;'S;=`#2u<%lO#2d0/l#2xP;=`<%l#2d05W#3OP;=`<%l#1p01O#3YW([!bT0/lOY#3RZw#3Rwx#2dx#O#3R#O#P#2d#P;'S#3R;'S;=`#3r<%lO#3R01O#3uP;=`<%l#3R06j#3{P;=`<%l#0n05x#4X]$j&j(XpT0/lOY#4OYZ&cZr#4Ors#1ps!^#4O!^!_#5Q!_#O#4O#O#P#1p#P#o#4O#o#p#5Q#p;'S#4O;'S;=`#5w<%lO#4O00^#5XW(XpT0/lOY#5QZr#5Qrs#2ds#O#5Q#O#P#2d#P;'S#5Q;'S;=`#5q<%lO#5Q00^#5tP;=`<%l#5Q05x#5zP;=`<%l#4O01p#6WY(Xp([!bT0/lOY#5}Zr#5}rs#3Rsw#5}wx#5Qx#O#5}#O#P#2d#P;'S#5};'S;=`#6v<%lO#5}01p#6yP;=`<%l#5}07[#7PP;=`<%l#/d)3h#7ab$j&j$R(Ch(Xp([!b!X7`OY!;ZYZ&cZr!;Zrs!<nsw!;Zwx!Lcx!P!;Z!P!Q#&`!Q!^!;Z!^!_#(i!_!}!;Z!}#O#,f#O#P!Dy#P#o!;Z#o#p#(i#p;'S!;Z;'S;=`#-w<%lO!;ZAt#8vb$[#t$j&j(Xp([!b!X7`OY!;ZYZ&cZr!;Zrs!<nsw!;Zwx!Lcx!P!;Z!P!Q#&`!Q!^!;Z!^!_#(i!_!}!;Z!}#O#,f#O#P!Dy#P#o!;Z#o#p#(i#p;'S!;Z;'S;=`#-w<%lO!;Z'Ad#:Zp$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!O%Z!O!P!3Y!P!Q%Z!Q![#<_![!^%Z!^!_*g!_!g%Z!g!h!4|!h#O%Z#O#P&c#P#R%Z#R#S#<_#S#U%Z#U#V#?i#V#X%Z#X#Y!4|#Y#b%Z#b#c#>_#c#d#Bq#d#l%Z#l#m#Es#m#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#<jk$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!O%Z!O!P!3Y!P!Q%Z!Q![#<_![!^%Z!^!_*g!_!g%Z!g!h!4|!h#O%Z#O#P&c#P#R%Z#R#S#<_#S#X%Z#X#Y!4|#Y#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#>j_$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#?rd$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#AQ!R!S#AQ!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#AQ#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#A]f$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#AQ!R!S#AQ!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#AQ#S#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Bzc$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!Y#DV!Y!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#DV#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Dbe$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!Y#DV!Y!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#DV#S#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#E|g$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![#Ge![!^%Z!^!_*g!_!c%Z!c!i#Ge!i#O%Z#O#P&c#P#R%Z#R#S#Ge#S#T%Z#T#Z#Ge#Z#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Gpi$j&j(Xp([!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![#Ge![!^%Z!^!_*g!_!c%Z!c!i#Ge!i#O%Z#O#P&c#P#R%Z#R#S#Ge#S#T%Z#T#Z#Ge#Z#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z*)x#Il_!g$b$j&j$P)Lv(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z)[#Jv_al$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z04f#LS^h#)`#R-<U(Xp([!b$o7`OY*gZr*grs'}sw*gwx)rx!P*g!P!Q#MO!Q!^*g!^!_#Mt!_!`$ f!`#O*g#P;'S*g;'S;=`+Z<%lO*g(n#MXX$l&j(Xp([!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g(El#M}Z#s(Ch(Xp([!bOY*gZr*grs'}sw*gwx)rx!_*g!_!`#Np!`#O*g#P;'S*g;'S;=`+Z<%lO*g(El#NyX$R(Ch(Xp([!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g(El$ oX#t(Ch(Xp([!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g*)x$!ga#`*!Y$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`0z!`!a$#l!a#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(K[$#w_#l(Cl$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z*)x$%Vag!*r#t(Ch$g#|$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`$&[!`!a$'f!a#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW$&g_#t(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW$'qa#s(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`!a$(v!a#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW$)R`#s(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(Kd$*`a(s(Ct$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!a%Z!a!b$+e!b#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW$+p`$j&j#|(Ch(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z%#`$,}_!|$Ip$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z04f$.X_!S0,v$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(n$/]Z$j&jO!^$0O!^!_$0f!_#i$0O#i#j$0k#j#l$0O#l#m$2^#m#o$0O#o#p$0f#p;'S$0O;'S;=`$4i<%lO$0O(n$0VT_#S$j&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c#S$0kO_#S(n$0p[$j&jO!Q&c!Q![$1f![!^&c!_!c&c!c!i$1f!i#T&c#T#Z$1f#Z#o&c#o#p$3|#p;'S&c;'S;=`&w<%lO&c(n$1kZ$j&jO!Q&c!Q![$2^![!^&c!_!c&c!c!i$2^!i#T&c#T#Z$2^#Z#o&c#p;'S&c;'S;=`&w<%lO&c(n$2cZ$j&jO!Q&c!Q![$3U![!^&c!_!c&c!c!i$3U!i#T&c#T#Z$3U#Z#o&c#p;'S&c;'S;=`&w<%lO&c(n$3ZZ$j&jO!Q&c!Q![$0O![!^&c!_!c&c!c!i$0O!i#T&c#T#Z$0O#Z#o&c#p;'S&c;'S;=`&w<%lO&c#S$4PR!Q![$4Y!c!i$4Y#T#Z$4Y#S$4]S!Q![$4Y!c!i$4Y#T#Z$4Y#q#r$0f(n$4lP;=`<%l$0O#1[$4z_!Y#)l$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW$6U`#y(Ch$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z+;p$7c_$j&j(Xp([!b(b+4QOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z07[$8qk$j&j(Xp([!b(U,2j$`#t(f$I[OY%ZYZ&cZr%Zrs&}st%Ztu$8buw%Zwx(rx}%Z}!O$:f!O!Q%Z!Q![$8b![!^%Z!^!_*g!_!c%Z!c!}$8b!}#O%Z#O#P&c#P#R%Z#R#S$8b#S#T%Z#T#o$8b#o#p*g#p$g%Z$g;'S$8b;'S;=`$<l<%lO$8b+d$:qk$j&j(Xp([!b$`#tOY%ZYZ&cZr%Zrs&}st%Ztu$:fuw%Zwx(rx}%Z}!O$:f!O!Q%Z!Q![$:f![!^%Z!^!_*g!_!c%Z!c!}$:f!}#O%Z#O#P&c#P#R%Z#R#S$:f#S#T%Z#T#o$:f#o#p*g#p$g%Z$g;'S$:f;'S;=`$<f<%lO$:f+d$<iP;=`<%l$:f07[$<oP;=`<%l$8b#Jf$<{X!_#Hb(Xp([!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g,#x$=sa(z+JY$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`Ka!`#O%Z#O#P&c#P#o%Z#o#p*g#p#q$+e#q;'S%Z;'S;=`+a<%lO%Z)>v$?V_!^(CdvBr$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z?O$@a_!q7`$j&j(Xp([!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z07[$Aq|$j&j(Xp([!b'}0/l$^#t(U,2j(f$I[OX%ZXY+gYZ&cZ[+g[p%Zpq+gqr%Zrs&}st%ZtuEruw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Er![!^%Z!^!_*g!_!c%Z!c!}Er!}#O%Z#O#P&c#P#R%Z#R#SEr#S#T%Z#T#oEr#o#p*g#p$f%Z$f$g+g$g#BYEr#BY#BZ$A`#BZ$ISEr$IS$I_$A`$I_$JTEr$JT$JU$A`$JU$KVEr$KV$KW$A`$KW&FUEr&FU&FV$A`&FV;'SEr;'S;=`I|<%l?HTEr?HT?HU$A`?HUOEr07[$D|k$j&j(Xp([!b(O0/l$^#t(U,2j(f$I[OY%ZYZ&cZr%Zrs&}st%ZtuEruw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Er![!^%Z!^!_*g!_!c%Z!c!}Er!}#O%Z#O#P&c#P#R%Z#R#SEr#S#T%Z#T#oEr#o#p*g#p$g%Z$g;'SEr;'S;=`I|<%lOEr",tokenizers:[p5,O5,g5,m5,2,3,4,5,6,7,8,9,10,11,12,13,14,d5,new ln("$S~RRtu[#O#Pg#S#T#|~_P#o#pb~gOx~~jVO#i!P#i#j!U#j#l!P#l#m!q#m;'S!P;'S;=`#v<%lO!P~!UO!U~~!XS!Q![!e!c!i!e#T#Z!e#o#p#Z~!hR!Q![!q!c!i!q#T#Z!q~!tR!Q![!}!c!i!}#T#Z!}~#QR!Q![!P!c!i!P#T#Z!P~#^R!Q![#g!c!i#g#T#Z#g~#jS!Q![#g!c!i#g#T#Z#g#q#r!P~#yP;=`<%l!P~$RO(d~~",141,341),new ln("j~RQYZXz{^~^O(R~~aP!P!Qd~iO(S~~",25,324)],topRules:{Script:[0,7],SingleExpression:[1,277],SingleClassItem:[2,278]},dialects:{jsx:0,ts:15152},dynamicPrecedences:{80:1,82:1,94:1,170:1,200:1},specialized:[{term:328,get:i=>y5[i]||-1},{term:344,get:i=>S5[i]||-1},{term:95,get:i=>x5[i]||-1}],tokenPrec:15178});var Vx=[it("function ${name}(${params}) {\n ${}\n}",{label:"function",detail:"definition",type:"keyword"}),it("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n ${}\n}",{label:"for",detail:"loop",type:"keyword"}),it("for (let ${name} of ${collection}) {\n ${}\n}",{label:"for",detail:"of loop",type:"keyword"}),it("do {\n ${}\n} while (${})",{label:"do",detail:"loop",type:"keyword"}),it("while (${}) {\n ${}\n}",{label:"while",detail:"loop",type:"keyword"}),it(`try { + \${} +} catch (\${error}) { + \${} +}`,{label:"try",detail:"/ catch block",type:"keyword"}),it("if (${}) {\n ${}\n}",{label:"if",detail:"block",type:"keyword"}),it(`if (\${}) { + \${} +} else { + \${} +}`,{label:"if",detail:"/ else block",type:"keyword"}),it(`class \${name} { + constructor(\${params}) { + \${} + } +}`,{label:"class",detail:"definition",type:"keyword"}),it('import {${names}} from "${module}"\n${}',{label:"import",detail:"named",type:"keyword"}),it('import ${name} from "${module}"\n${}',{label:"import",detail:"default",type:"keyword"})],w5=Vx.concat([it("interface ${name} {\n ${}\n}",{label:"interface",detail:"definition",type:"keyword"}),it("type ${name} = ${type}",{label:"type",detail:"definition",type:"keyword"}),it("enum ${name} {\n ${}\n}",{label:"enum",detail:"definition",type:"keyword"})]),Ix=new rs,Yx=new Set(["Script","Block","FunctionExpression","FunctionDeclaration","ArrowFunction","MethodDeclaration","ForStatement"]);function el(i){return(e,t)=>{let n=e.node.getChild("VariableDefinition");return n&&t(n,i),!0}}var k5=["FunctionDeclaration"],Q5={FunctionDeclaration:el("function"),ClassDeclaration:el("class"),ClassExpression:()=>!0,EnumDeclaration:el("constant"),TypeAliasDeclaration:el("type"),NamespaceDeclaration:el("namespace"),VariableDefinition(i,e){i.matchContext(k5)||e(i,"variable")},TypeDefinition(i,e){e(i,"type")},__proto__:null};function zx(i,e){let t=Ix.get(e);if(t)return t;let n=[],r=!0;function s(o,l){let a=i.sliceString(o.from,o.to);n.push({label:a,type:l})}return e.cursor(F.IncludeAnonymous).iterate(o=>{if(r)r=!1;else if(o.name){let l=Q5[o.name];if(l&&l(o,s)||Yx.has(o.name))return!1}else if(o.to-o.from>8192){for(let l of zx(i,o.node))n.push(l);return!1}}),Ix.set(e,n),n}var Ux=/^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/,Wx=["TemplateString","String","RegExp","LineComment","BlockComment","VariableDefinition","TypeDefinition","Label","PropertyDefinition","PropertyName","PrivatePropertyDefinition","PrivatePropertyName","JSXText","JSXAttributeValue","JSXOpenTag","JSXCloseTag","JSXSelfClosingTag",".","?."];function v5(i){let e=ee(i.state).resolveInner(i.pos,-1);if(Wx.indexOf(e.name)>-1)return null;let t=e.name=="VariableName"||e.to-e.from<20&&Ux.test(i.state.sliceDoc(e.from,e.to));if(!t&&!i.explicit)return null;let n=[];for(let r=e;r;r=r.parent)Yx.has(r.name)&&(n=n.concat(zx(i.state.doc,r)));return{options:n,from:t?e.from:i.pos,validFor:Ux}}var qt=Hi.define({name:"javascript",parser:jx.configure({props:[$i.add({IfStatement:os({except:/^\s*({|else\b)/}),TryStatement:os({except:/^\s*({|catch\b|finally\b)/}),LabeledStatement:Pb,SwitchBody:i=>{let e=i.textAfter,t=/^\s*\}/.test(e),n=/^\s*(case|default)\b/.test(e);return i.baseIndent+(t?0:n?1:2)*i.unit},Block:vb({closing:"}"}),ArrowFunction:i=>i.baseIndent+i.unit,"TemplateString BlockComment":()=>null,"Statement Property":os({except:/^\s*{/}),JSXElement(i){let e=/^\s*<\//.test(i.textAfter);return i.lineIndent(i.node.from)+(e?0:i.unit)},JSXEscape(i){let e=/\s*\}/.test(i.textAfter);return i.lineIndent(i.node.from)+(e?0:i.unit)},"JSXOpenTag JSXSelfClosingTag"(i){return i.column(i.node.from)+i.unit}}),fi.add({"Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType":Sc,BlockComment(i){return{from:i.from+2,to:i.to-2}},JSXElement(i){let e=i.firstChild;if(!e||e.name=="JSXSelfClosingTag")return null;let t=i.lastChild;return{from:e.to,to:t.type.isError?i.to:t.from}},"JSXSelfClosingTag JSXOpenTag"(i){var e;let t=(e=i.firstChild)===null||e===void 0?void 0:e.nextSibling,n=i.lastChild;return!t||t.type.isError?null:{from:t.to,to:n.type.isError?i.to:n.from}}})]}),languageData:{closeBrackets:{brackets:["(","[","{","'",'"',"`"]},commentTokens:{line:"//",block:{open:"/*",close:"*/"}},indentOnInput:/^\s*(?:case |default:|\{|\}|<\/)$/,wordChars:"$"}}),Nx={test:i=>/^JSX/.test(i.name),facet:Mo({commentTokens:{block:{open:"{/*",close:"*/}"}}})},aO=qt.configure({dialect:"ts"},"typescript"),cO=qt.configure({dialect:"jsx",props:[bc.add(i=>i.isTop?[Nx]:void 0)]}),hO=qt.configure({dialect:"jsx ts",props:[bc.add(i=>i.isTop?[Nx]:void 0)]},"typescript"),qx=i=>({label:i,type:"keyword"}),Gx="break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(qx),$5=Gx.concat(["declare","implements","private","protected","public"].map(qx));function Fx(i={}){let e=i.jsx?i.typescript?hO:cO:i.typescript?aO:qt,t=i.typescript?w5.concat($5):Vx.concat(Gx);return new Yt(e,[qt.data.of({autocomplete:Hy(Wx,up(t))}),qt.data.of({autocomplete:v5}),i.jsx?T5:[]])}function P5(i){for(;;){if(i.name=="JSXOpenTag"||i.name=="JSXSelfClosingTag"||i.name=="JSXFragmentTag")return i;if(i.name=="JSXEscape"||!i.parent)return null;i=i.parent}}function Bx(i,e,t=i.length){for(let n=e?.firstChild;n;n=n.nextSibling)if(n.name=="JSXIdentifier"||n.name=="JSXBuiltin"||n.name=="JSXNamespacedName"||n.name=="JSXMemberExpression")return i.sliceString(n.from,Math.min(n.to,t));return""}var C5=typeof navigator=="object"&&/Android\b/.test(navigator.userAgent),T5=M.inputHandler.of((i,e,t,n,r)=>{if((C5?i.composing:i.compositionStarted)||i.state.readOnly||e!=t||n!=">"&&n!="/"||!qt.isActiveAt(i.state,e,-1))return!1;let s=r(),{state:o}=s,l=o.changeByRange(a=>{var c;let{head:h}=a,f=ee(o).resolveInner(h-1,-1),u;if(f.name=="JSXStartTag"&&(f=f.parent),!(o.doc.sliceString(h-1,h)!=n||f.name=="JSXAttributeValue"&&f.to>h)){if(n==">"&&f.name=="JSXFragmentTag")return{range:a,changes:{from:h,insert:"</>"}};if(n=="/"&&f.name=="JSXStartCloseTag"){let d=f.parent,p=d.parent;if(p&&d.from==h-2&&((u=Bx(o.doc,p.firstChild,h))||((c=p.firstChild)===null||c===void 0?void 0:c.name)=="JSXFragmentTag")){let O=`${u}>`;return{range:v.cursor(h+O.length,-1),changes:{from:h,insert:O}}}}else if(n==">"){let d=P5(f);if(d&&d.name=="JSXOpenTag"&&!/^\/?>|^<\//.test(o.doc.sliceString(h,h+2))&&(u=Bx(o.doc,d,h)))return{range:a,changes:{from:h,insert:`</${u}>`}}}}return{range:a}});return l.changes.empty?!1:(i.dispatch([s,o.update(l,{userEvent:"input.complete",scrollIntoView:!0})]),!0)});var tl=["_blank","_self","_top","_parent"],fO=["ascii","utf-8","utf-16","latin1","latin1"],uO=["get","post","put","delete"],dO=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],Qt=["true","false"],I={},A5={a:{attrs:{href:null,ping:null,type:null,media:null,target:tl,hreflang:null}},abbr:I,address:I,area:{attrs:{alt:null,coords:null,href:null,target:null,ping:null,media:null,hreflang:null,type:null,shape:["default","rect","circle","poly"]}},article:I,aside:I,audio:{attrs:{src:null,mediagroup:null,crossorigin:["anonymous","use-credentials"],preload:["none","metadata","auto"],autoplay:["autoplay"],loop:["loop"],controls:["controls"]}},b:I,base:{attrs:{href:null,target:tl}},bdi:I,bdo:I,blockquote:{attrs:{cite:null}},body:I,br:I,button:{attrs:{form:null,formaction:null,name:null,value:null,autofocus:["autofocus"],disabled:["autofocus"],formenctype:dO,formmethod:uO,formnovalidate:["novalidate"],formtarget:tl,type:["submit","reset","button"]}},canvas:{attrs:{width:null,height:null}},caption:I,center:I,cite:I,code:I,col:{attrs:{span:null}},colgroup:{attrs:{span:null}},command:{attrs:{type:["command","checkbox","radio"],label:null,icon:null,radiogroup:null,command:null,title:null,disabled:["disabled"],checked:["checked"]}},data:{attrs:{value:null}},datagrid:{attrs:{disabled:["disabled"],multiple:["multiple"]}},datalist:{attrs:{data:null}},dd:I,del:{attrs:{cite:null,datetime:null}},details:{attrs:{open:["open"]}},dfn:I,div:I,dl:I,dt:I,em:I,embed:{attrs:{src:null,type:null,width:null,height:null}},eventsource:{attrs:{src:null}},fieldset:{attrs:{disabled:["disabled"],form:null,name:null}},figcaption:I,figure:I,footer:I,form:{attrs:{action:null,name:null,"accept-charset":fO,autocomplete:["on","off"],enctype:dO,method:uO,novalidate:["novalidate"],target:tl}},h1:I,h2:I,h3:I,h4:I,h5:I,h6:I,head:{children:["title","base","link","style","meta","script","noscript","command"]},header:I,hgroup:I,hr:I,html:{attrs:{manifest:null}},i:I,iframe:{attrs:{src:null,srcdoc:null,name:null,width:null,height:null,sandbox:["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"],seamless:["seamless"]}},img:{attrs:{alt:null,src:null,ismap:null,usemap:null,width:null,height:null,crossorigin:["anonymous","use-credentials"]}},input:{attrs:{alt:null,dirname:null,form:null,formaction:null,height:null,list:null,max:null,maxlength:null,min:null,name:null,pattern:null,placeholder:null,size:null,src:null,step:null,value:null,width:null,accept:["audio/*","video/*","image/*"],autocomplete:["on","off"],autofocus:["autofocus"],checked:["checked"],disabled:["disabled"],formenctype:dO,formmethod:uO,formnovalidate:["novalidate"],formtarget:tl,multiple:["multiple"],readonly:["readonly"],required:["required"],type:["hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local","number","range","color","checkbox","radio","file","submit","image","reset","button"]}},ins:{attrs:{cite:null,datetime:null}},kbd:I,keygen:{attrs:{challenge:null,form:null,name:null,autofocus:["autofocus"],disabled:["disabled"],keytype:["RSA"]}},label:{attrs:{for:null,form:null}},legend:I,li:{attrs:{value:null}},link:{attrs:{href:null,type:null,hreflang:null,media:null,sizes:["all","16x16","16x16 32x32","16x16 32x32 64x64"]}},map:{attrs:{name:null}},mark:I,menu:{attrs:{label:null,type:["list","context","toolbar"]}},meta:{attrs:{content:null,charset:fO,name:["viewport","application-name","author","description","generator","keywords"],"http-equiv":["content-language","content-type","default-style","refresh"]}},meter:{attrs:{value:null,min:null,low:null,high:null,max:null,optimum:null}},nav:I,noscript:I,object:{attrs:{data:null,type:null,name:null,usemap:null,form:null,width:null,height:null,typemustmatch:["typemustmatch"]}},ol:{attrs:{reversed:["reversed"],start:null,type:["1","a","A","i","I"]},children:["li","script","template","ul","ol"]},optgroup:{attrs:{disabled:["disabled"],label:null}},option:{attrs:{disabled:["disabled"],label:null,selected:["selected"],value:null}},output:{attrs:{for:null,form:null,name:null}},p:I,param:{attrs:{name:null,value:null}},pre:I,progress:{attrs:{value:null,max:null}},q:{attrs:{cite:null}},rp:I,rt:I,ruby:I,samp:I,script:{attrs:{type:["text/javascript"],src:null,async:["async"],defer:["defer"],charset:fO}},section:I,select:{attrs:{form:null,name:null,size:null,autofocus:["autofocus"],disabled:["disabled"],multiple:["multiple"]}},slot:{attrs:{name:null}},small:I,source:{attrs:{src:null,type:null,media:null}},span:I,strong:I,style:{attrs:{type:["text/css"],media:null,scoped:null}},sub:I,summary:I,sup:I,table:I,tbody:I,td:{attrs:{colspan:null,rowspan:null,headers:null}},template:I,textarea:{attrs:{dirname:null,form:null,maxlength:null,name:null,placeholder:null,rows:null,cols:null,autofocus:["autofocus"],disabled:["disabled"],readonly:["readonly"],required:["required"],wrap:["soft","hard"]}},tfoot:I,th:{attrs:{colspan:null,rowspan:null,headers:null,scope:["row","col","rowgroup","colgroup"]}},thead:I,time:{attrs:{datetime:null}},title:I,tr:I,track:{attrs:{src:null,label:null,default:null,kind:["subtitles","captions","descriptions","chapters","metadata"],srclang:null}},ul:{children:["li","script","template","ul","ol"]},var:I,video:{attrs:{src:null,poster:null,width:null,height:null,crossorigin:["anonymous","use-credentials"],preload:["auto","metadata","none"],autoplay:["autoplay"],mediagroup:["movie"],muted:["muted"],controls:["controls"]}},wbr:I},ew={accesskey:null,class:null,contenteditable:Qt,contextmenu:null,dir:["ltr","rtl","auto"],draggable:["true","false","auto"],dropzone:["copy","move","link","string:","file:"],hidden:["hidden"],id:null,inert:["inert"],itemid:null,itemprop:null,itemref:null,itemscope:["itemscope"],itemtype:null,lang:["ar","bn","de","en-GB","en-US","es","fr","hi","id","ja","pa","pt","ru","tr","zh"],spellcheck:Qt,autocorrect:Qt,autocapitalize:Qt,style:null,tabindex:null,title:null,translate:["yes","no"],rel:["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"],role:"alert application article banner button cell checkbox complementary contentinfo dialog document feed figure form grid gridcell heading img list listbox listitem main navigation region row rowgroup search switch tab table tabpanel textbox timer".split(" "),"aria-activedescendant":null,"aria-atomic":Qt,"aria-autocomplete":["inline","list","both","none"],"aria-busy":Qt,"aria-checked":["true","false","mixed","undefined"],"aria-controls":null,"aria-describedby":null,"aria-disabled":Qt,"aria-dropeffect":null,"aria-expanded":["true","false","undefined"],"aria-flowto":null,"aria-grabbed":["true","false","undefined"],"aria-haspopup":Qt,"aria-hidden":Qt,"aria-invalid":["true","false","grammar","spelling"],"aria-label":null,"aria-labelledby":null,"aria-level":null,"aria-live":["off","polite","assertive"],"aria-multiline":Qt,"aria-multiselectable":Qt,"aria-owns":null,"aria-posinset":null,"aria-pressed":["true","false","mixed","undefined"],"aria-readonly":Qt,"aria-relevant":null,"aria-required":Qt,"aria-selected":["true","false","undefined"],"aria-setsize":null,"aria-sort":["ascending","descending","none","other"],"aria-valuemax":null,"aria-valuemin":null,"aria-valuenow":null,"aria-valuetext":null},tw="beforeunload copy cut dragstart dragover dragleave dragenter dragend drag paste focus blur change click load mousedown mouseenter mouseleave mouseup keydown keyup resize scroll unload".split(" ").map(i=>"on"+i);for(let i of tw)ew[i]=null;var ir=class{constructor(e,t){this.tags={...A5,...e},this.globalAttrs={...ew,...t},this.allTags=Object.keys(this.tags),this.globalAttrNames=Object.keys(this.globalAttrs)}};ir.default=new ir;function nr(i,e,t=i.length){if(!e)return"";let n=e.firstChild,r=n&&n.getChild("TagName");return r?i.sliceString(r.from,Math.min(r.to,t)):""}function gs(i,e=!1){for(;i;i=i.parent)if(i.name=="Element")if(e)e=!1;else return i;return null}function iw(i,e,t){let n=t.tags[nr(i,gs(e))];return n?.children||t.allTags}function pO(i,e){let t=[];for(let n=gs(e);n&&!n.type.isTop;n=gs(n.parent)){let r=nr(i,n);if(r&&n.lastChild.name=="CloseTag")break;r&&t.indexOf(r)<0&&(e.name=="EndTag"||e.from>=n.firstChild.to)&&t.push(r)}return t}var nw=/^[:\-\.\w\u00b7-\uffff]*$/;function Hx(i,e,t,n,r){let s=/\s*>/.test(i.sliceDoc(r,r+5))?"":">",o=gs(t,t.name=="StartTag"||t.name=="TagName");return{from:n,to:r,options:iw(i.doc,o,e).map(l=>({label:l,type:"type"})).concat(pO(i.doc,t).map((l,a)=>({label:"/"+l,apply:"/"+l+s,type:"type",boost:99-a}))),validFor:/^\/?[:\-\.\w\u00b7-\uffff]*$/}}function Kx(i,e,t,n){let r=/\s*>/.test(i.sliceDoc(n,n+5))?"":">";return{from:t,to:n,options:pO(i.doc,e).map((s,o)=>({label:s,apply:s+r,type:"type",boost:99-o})),validFor:nw}}function M5(i,e,t,n){let r=[],s=0;for(let o of iw(i.doc,t,e))r.push({label:"<"+o,type:"type"});for(let o of pO(i.doc,t))r.push({label:"</"+o+">",type:"type",boost:99-s++});return{from:n,to:n,options:r,validFor:/^<\/?[:\-\.\w\u00b7-\uffff]*$/}}function R5(i,e,t,n,r){let s=gs(t),o=s?e.tags[nr(i.doc,s)]:null,l=o&&o.attrs?Object.keys(o.attrs):[],a=o&&o.globalAttrs===!1?l:l.length?l.concat(e.globalAttrNames):e.globalAttrNames;return{from:n,to:r,options:a.map(c=>({label:c,type:"property"})),validFor:nw}}function X5(i,e,t,n,r){var s;let o=(s=t.parent)===null||s===void 0?void 0:s.getChild("AttributeName"),l=[],a;if(o){let c=i.sliceDoc(o.from,o.to),h=e.globalAttrs[c];if(!h){let f=gs(t),u=f?e.tags[nr(i.doc,f)]:null;h=u?.attrs&&u.attrs[c]}if(h){let f=i.sliceDoc(n,r).toLowerCase(),u='"',d='"';/^['"]/.test(f)?(a=f[0]=='"'?/^[^"]*$/:/^[^']*$/,u="",d=i.sliceDoc(r,r+1)==f[0]?"":f[0],f=f.slice(1),n++):a=/^[^\s<>='"]*$/;for(let p of h)l.push({label:p,apply:u+p+d,type:"constant"})}}return{from:n,to:r,options:l,validFor:a}}function rw(i,e){let{state:t,pos:n}=e,r=ee(t).resolveInner(n,-1),s=r.resolve(n);for(let o=n,l;s==r&&(l=r.childBefore(o));){let a=l.lastChild;if(!a||!a.type.isError||a.from<a.to)break;s=r=l,o=a.from}return r.name=="TagName"?r.parent&&/CloseTag$/.test(r.parent.name)?Kx(t,r,r.from,n):Hx(t,i,r,r.from,n):r.name=="StartTag"||r.name=="IncompleteTag"?Hx(t,i,r,n,n):r.name=="StartCloseTag"||r.name=="IncompleteCloseTag"?Kx(t,r,n,n):r.name=="OpenTag"||r.name=="SelfClosingTag"||r.name=="AttributeName"?R5(t,i,r,r.name=="AttributeName"?r.from:n,n):r.name=="Is"||r.name=="AttributeValue"||r.name=="UnquotedAttributeValue"?X5(t,i,r,r.name=="Is"?n:r.from,n):e.explicit&&(s.name=="Element"||s.name=="Text"||s.name=="Document")?M5(t,i,r,n):null}function sw(i){return rw(ir.default,i)}function E5(i){let{extraTags:e,extraGlobalAttributes:t}=i,n=t||e?new ir(e,t):ir.default;return r=>rw(n,r)}var Z5=qt.parser.configure({top:"SingleExpression"}),ow=[{tag:"script",attrs:i=>i.type=="text/typescript"||i.lang=="ts",parser:aO.parser},{tag:"script",attrs:i=>i.type=="text/babel"||i.type=="text/jsx",parser:cO.parser},{tag:"script",attrs:i=>i.type=="text/typescript-jsx",parser:hO.parser},{tag:"script",attrs(i){return/^(importmap|speculationrules|application\/(.+\+)?json)$/i.test(i.type)},parser:Z5},{tag:"script",attrs(i){return!i.type||/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i.test(i.type)},parser:qt.parser},{tag:"style",attrs(i){return(!i.lang||i.lang=="css")&&(!i.type||/^(text\/)?(x-)?(stylesheet|css)$/i.test(i.type))},parser:Jo.parser}],lw=[{name:"style",parser:Jo.parser.configure({top:"Styles"})}].concat(tw.map(i=>({name:i,parser:qt.parser}))),aw=Hi.define({name:"html",parser:wx.configure({props:[$i.add({Element(i){let e=/^(\s*)(<\/)?/.exec(i.textAfter);return i.node.to<=i.pos+e[0].length?i.continue():i.lineIndent(i.node.from)+(e[2]?0:i.unit)},"OpenTag CloseTag SelfClosingTag"(i){return i.column(i.node.from)+i.unit},Document(i){if(i.pos+/\s*/.exec(i.textAfter)[0].length<i.node.to)return i.continue();let e=null,t;for(let n=i.node;;){let r=n.lastChild;if(!r||r.name!="Element"||r.to!=n.to)break;e=n=r}return e&&!((t=e.lastChild)&&(t.name=="CloseTag"||t.name=="SelfClosingTag"))?i.lineIndent(e.from)+i.unit:null}}),fi.add({Element(i){let e=i.firstChild,t=i.lastChild;return!e||e.name!="OpenTag"?null:{from:e.to,to:t.name=="CloseTag"?t.from:i.to}}}),_d.add({"OpenTag CloseTag":i=>i.getChild("TagName")})]}),languageData:{commentTokens:{block:{open:"<!--",close:"-->"}},indentOnInput:/^\s*<\/\w+\W$/,wordChars:"-_"}}),rh=aw.configure({wrap:tO(ow,lw)});function cw(i={}){let e="",t;i.matchClosingTags===!1&&(e="noMatch"),i.selfClosingTags===!0&&(e=(e?e+" ":"")+"selfClosing"),(i.nestedLanguages&&i.nestedLanguages.length||i.nestedAttributes&&i.nestedAttributes.length)&&(t=tO((i.nestedLanguages||[]).concat(ow),(i.nestedAttributes||[]).concat(lw)));let n=t?aw.configure({wrap:t,dialect:e}):e?rh.configure({dialect:e}):rh;return new Yt(n,[rh.data.of({autocomplete:E5(i)}),i.autoCloseTags!==!1?L5:[],Fx().support,_x().support])}var Jx=new Set("area base br col command embed frame hr img input keygen link meta param source track wbr menuitem".split(" "));function _5(i,e,t){for(var n;;){if(((n=e.lastChild)===null||n===void 0?void 0:n.name)!="CloseTag")return!1;let r=e.parent;if(!r||nr(i,r)!=t)return!0;e=r}}var L5=M.inputHandler.of((i,e,t,n,r)=>{if(i.composing||i.state.readOnly||e!=t||n!=">"&&n!="/"||!rh.isActiveAt(i.state,e,-1))return!1;let s=r(),{state:o}=s,l=o.changeByRange(a=>{var c;let h=o.doc.sliceString(a.from-1,a.to)==n,{head:f}=a,u=ee(o).resolveInner(f,-1),d;if(h&&n==">"&&u.name=="EndTag"){let p=u.parent;if((d=nr(o.doc,p.parent,f))&&!Jx.has(d)&&!_5(o.doc,p.parent,d)){let O=f+(o.doc.sliceString(f,f+1)===">"?1:0),g=`</${d}>`;return{range:a,changes:{from:f,to:O,insert:g}}}}else if(h&&n=="/"&&u.name=="IncompleteCloseTag"){let p=u.parent;if(u.from==f-2&&((c=p.lastChild)===null||c===void 0?void 0:c.name)!="CloseTag"&&(d=nr(o.doc,p,f))&&!Jx.has(d)){let O=f+(o.doc.sliceString(f,f+1)===">"?1:0),g=`${d}>`;return{range:v.cursor(f+g.length,-1),changes:{from:f,to:O,insert:g}}}}return{range:a}});return l.changes.empty?!1:(i.dispatch([s,o.update(l,{userEvent:"input.complete",scrollIntoView:!0})]),!0)});var uw=Mo({commentTokens:{block:{open:"<!--",close:"-->"}}}),dw=new U,pw=WS.configure({props:[fi.add(i=>!i.is("Block")||i.is("Document")||mO(i)!=null||D5(i)?void 0:(e,t)=>({from:t.doc.lineAt(e.from).to,to:e.to})),dw.add(mO),$i.add({Document:()=>null}),Fi.add({Document:uw})]});function mO(i){let e=/^(?:ATX|Setext)Heading(\d)$/.exec(i.name);return e?+e[1]:void 0}function D5(i){return i.name=="OrderedList"||i.name=="BulletList"}function j5(i,e){let t=i;for(;;){let n=t.nextSibling,r;if(!n||(r=mO(n.type))!=null&&r<=e)break;t=n}return t.to}var I5=Ed.of((i,e,t)=>{for(let n=ee(i).resolveInner(t,-1);n&&!(n.from<e);n=n.parent){let r=n.type.prop(dw);if(r==null)continue;let s=j5(n,r);if(s>t)return{from:t,to:s}}return null});function bO(i){return new tt(uw,i,[],"markdown")}var U5=bO(pw),B5=pw.configure([GS,KS,HS,JS,{props:[fi.add({Table:(i,e)=>({from:e.doc.lineAt(i.from).to,to:i.to})})]}]),sh=bO(B5);function V5(i,e){return t=>{if(t&&i){let n=null;if(t=/\S*/.exec(t)[0],typeof i=="function"?n=i(t):n=Ao.matchLanguageName(i,t,!0),n instanceof Ao)return n.support?n.support.language.parser:Co.getSkippingParser(n.load());if(n)return n.parser}return e?e.parser:null}}var il=class{constructor(e,t,n,r,s,o,l){this.node=e,this.from=t,this.to=n,this.spaceBefore=r,this.spaceAfter=s,this.type=o,this.item=l}blank(e,t=!0){let n=this.spaceBefore+(this.node.name=="Blockquote"?">":"");if(e!=null){for(;n.length<e;)n+=" ";return n}else{for(let r=this.to-this.from-n.length-this.spaceAfter.length;r>0;r--)n+=" ";return n+(t?this.spaceAfter:"")}}marker(e,t){let n=this.node.name=="OrderedList"?String(+gw(this.item,e)[2]+t):"";return this.spaceBefore+n+this.type+this.spaceAfter}};function Ow(i,e){let t=[],n=[];for(let r=i;r;r=r.parent){if(r.name=="FencedCode")return n;(r.name=="ListItem"||r.name=="Blockquote")&&t.push(r)}for(let r=t.length-1;r>=0;r--){let s=t[r],o,l=e.lineAt(s.from),a=s.from-l.from;if(s.name=="Blockquote"&&(o=/^ *>( ?)/.exec(l.text.slice(a))))n.push(new il(s,a,a+o[0].length,"",o[1],">",null));else if(s.name=="ListItem"&&s.parent.name=="OrderedList"&&(o=/^( *)\d+([.)])( *)/.exec(l.text.slice(a)))){let c=o[3],h=o[0].length;c.length>=4&&(c=c.slice(0,c.length-4),h-=4),n.push(new il(s.parent,a,a+h,o[1],c,o[2],s))}else if(s.name=="ListItem"&&s.parent.name=="BulletList"&&(o=/^( *)([-+*])( {1,4}\[[ xX]\])?( +)/.exec(l.text.slice(a)))){let c=o[4],h=o[0].length;c.length>4&&(c=c.slice(0,c.length-4),h-=4);let f=o[2];o[3]&&(f+=o[3].replace(/[xX]/," ")),n.push(new il(s.parent,a,a+h,o[1],c,f,s))}}return n}function gw(i,e){return/^(\s*)(\d+)(?=[.)])/.exec(e.sliceString(i.from,i.from+10))}function OO(i,e,t,n=0){for(let r=-1,s=i;;){if(s.name=="ListItem"){let l=gw(s,e),a=+l[2];if(r>=0){if(a!=r+1)return;t.push({from:s.from+l[1].length,to:s.from+l[0].length,insert:String(r+2+n)})}r=a}let o=s.nextSibling;if(!o)break;s=o}}function yO(i,e){let t=/^[ \t]*/.exec(i)[0].length;if(!t||e.facet(Ji)!=" ")return i;let n=Ke(i,4,t),r="";for(let s=n;s>0;)s>=4?(r+=" ",s-=4):(r+=" ",s--);return r+i.slice(t)}var Y5=(i={})=>({state:e,dispatch:t})=>{let n=ee(e),{doc:r}=e,s=null,o=e.changeByRange(l=>{if(!l.empty||!sh.isActiveAt(e,l.from,-1)&&!sh.isActiveAt(e,l.from,1))return s={range:l};let a=l.from,c=r.lineAt(a),h=Ow(n.resolveInner(a,-1),r);for(;h.length&&h[h.length-1].from>a-c.from;)h.pop();if(!h.length)return s={range:l};let f=h[h.length-1];if(f.to-f.spaceAfter.length>a-c.from)return s={range:l};let u=a>=f.to-f.spaceAfter.length&&!/\S/.test(c.text.slice(f.to));if(f.item&&u){if(f.item.from<c.from&&!/^[\s>]*$/.test(c.text.slice(0,f.to)))return s={range:l};let b=f.node.firstChild,w=f.node.getChild("ListItem","ListItem");if(b.to>=a||w&&w.to<a||c.from>0&&!/[^\s>]/.test(r.lineAt(c.from-1).text)||i.nonTightLists===!1){let x=h.length>1?h[h.length-2]:null,X,C="";x&&x.item?(X=c.from+x.from,C=x.marker(r,1)):X=c.from+(x?x.to:0);let T=[{from:X,to:a,insert:C}];return f.node.name=="OrderedList"&&OO(f.item,r,T,-2),x&&x.node.name=="OrderedList"&&OO(x.item,r,T),{range:v.cursor(X+C.length),changes:T}}else{let x=fw(h,e,c);return{range:v.cursor(a+x.length+1),changes:{from:c.from,insert:x+e.lineBreak}}}}if(f.node.name=="Blockquote"&&u&&c.from){let b=r.lineAt(c.from-1),w=/>\s*$/.exec(b.text);if(w&&w.index==f.from){let x=e.changes([{from:b.from+w.index,to:b.to},{from:c.from+f.from,to:c.to}]);return{range:l.map(x),changes:x}}}let d=[];f.node.name=="OrderedList"&&OO(f.item,r,d);let p=f.item&&f.item.from<c.from,O="";if(!p||/^[\s\d.)\-+*>]*/.exec(c.text)[0].length>=f.to)for(let b=0,w=h.length-1;b<=w;b++)O+=b==w&&!p?h[b].marker(r,1):h[b].blank(b<w?Ke(c.text,4,h[b+1].from)-O.length:null);let g=a;for(;g>c.from&&/\s/.test(c.text.charAt(g-c.from-1));)g--;return O=yO(O,e),W5(f.node,e.doc)&&(O=fw(h,e,c)+e.lineBreak+O),d.push({from:g,to:a,insert:e.lineBreak+O}),{range:v.cursor(g+O.length+1),changes:d}});return s?!1:(t(e.update(o,{scrollIntoView:!0,userEvent:"input"})),!0)},z5=Y5();function hw(i){return i.name=="QuoteMark"||i.name=="ListMark"}function W5(i,e){if(i.name!="OrderedList"&&i.name!="BulletList")return!1;let t=i.firstChild,n=i.getChild("ListItem","ListItem");if(!n)return!1;let r=e.lineAt(t.to),s=e.lineAt(n.from),o=/^[\s>]*$/.test(r.text);return r.number+(o?0:1)<s.number}function fw(i,e,t){let n="";for(let r=0,s=i.length-2;r<=s;r++)n+=i[r].blank(r<s?Ke(t.text,4,i[r+1].from)-n.length:null,r<s);return yO(n,e)}function N5(i,e){let t=i.resolveInner(e,-1),n=e;hw(t)&&(n=t.from,t=t.parent);for(let r;r=t.childBefore(n);)if(hw(r))n=r.from;else if(r.name=="OrderedList"||r.name=="BulletList")t=r.lastChild,n=t.to;else break;return t}var q5=({state:i,dispatch:e})=>{let t=ee(i),n=null,r=i.changeByRange(s=>{let o=s.from,{doc:l}=i;if(s.empty&&sh.isActiveAt(i,s.from)){let a=l.lineAt(o),c=Ow(N5(t,o),l);if(c.length){let h=c[c.length-1],f=h.to-h.spaceAfter.length+(h.spaceAfter?1:0);if(o-a.from>f&&!/\S/.test(a.text.slice(f,o-a.from)))return{range:v.cursor(a.from+f),changes:{from:a.from+f,to:o}};if(o-a.from==f&&(h.item&&a.from<=h.item.from||/^[\s>]*$/.test(a.text.slice(0,h.to)))){let u=a.from+h.from;if(h.item&&h.node.from<h.item.from&&/\S/.test(a.text.slice(h.from,h.to))){let d=h.blank(Ke(a.text,4,h.to)-Ke(a.text,4,h.from));return u==a.from&&(d=yO(d,i)),{range:v.cursor(u+d.length),changes:{from:u,to:a.from+h.to,insert:d}}}if(u<o)return{range:v.cursor(u),changes:{from:u,to:o}}}}}return n={range:s}});return n?!1:(e(i.update(r,{scrollIntoView:!0,userEvent:"delete"})),!0)},G5=[{key:"Enter",run:z5},{key:"Backspace",run:q5}],mw=cw({matchClosingTags:!1});function F5(i={}){let{codeLanguages:e,defaultCodeLanguage:t,addKeymap:n=!0,base:{parser:r}=U5,completeHTMLTags:s=!0,pasteURLAsLink:o=!0,htmlTagLanguage:l=mw}=i;if(!(r instanceof qo))throw new RangeError("Base parser provided to `markdown` should be a Markdown parser");let a=i.extensions?[i.extensions]:[],c=[l.support,I5],h;o&&c.push(eR),t instanceof Yt?(c.push(t.support),h=t.language):t&&(h=t);let f=e||h?V5(e,h):void 0;a.push(NS({codeParser:f,htmlParser:l.language.parser})),n&&c.push(at.high(Qi.of(G5)));let u=bO(r.configure(a));return s&&c.push(u.data.of({autocomplete:H5})),new Yt(u,c)}function H5(i){let{state:e,pos:t}=i,n=/<[:\-\.\w\u00b7-\uffff]*$/.exec(e.sliceDoc(t-25,t));if(!n)return null;let r=ee(e).resolveInner(t,-1);for(;r&&!r.type.isTop;){if(r.name=="CodeBlock"||r.name=="FencedCode"||r.name=="ProcessingInstructionBlock"||r.name=="CommentBlock"||r.name=="Link"||r.name=="Image")return null;r=r.parent}return{from:t-n[0].length,to:t,options:K5(),validFor:/^<[:\-\.\w\u00b7-\uffff]*$/}}var gO=null;function K5(){if(gO)return gO;let i=sw(new fs(ne.create({extensions:mw}),0,!0));return gO=i?i.options:[]}var J5=/code|horizontalrule|html|link|comment|processing|escape|entity|image|mark|url/i,eR=M.domEventHandlers({paste:(i,e)=>{var t;let{main:n}=e.state.selection;if(n.empty)return!1;let r=(t=i.clipboardData)===null||t===void 0?void 0:t.getData("text/plain");if(!r||!/^(https?:\/\/|mailto:|xmpp:|www\.)/.test(r)||(/^www\./.test(r)&&(r="https://"+r),!sh.isActiveAt(e.state,n.from,1)))return!1;let s=ee(e.state),o=!1;return s.iterate({from:n.from,to:n.to,enter:l=>{(l.from>n.from||J5.test(l.name))&&(o=!0)},leave:l=>{l.to<n.to&&(o=!0)}}),o?!1:(e.dispatch({changes:[{from:n.from,insert:"["},{from:n.to,insert:`](${r})`}],userEvent:"input.paste",scrollIntoView:!0}),!0)}});var ms=function(i,e){let r=i,s=nl[e],o=null,l=0,a=null,c=[],h={},f=function(y,Q){l=r*4+17,o=(function(S){let $=new Array(S);for(let k=0;k<S;k+=1){$[k]=new Array(S);for(let P=0;P<S;P+=1)$[k][P]=null}return $})(l),u(0,0),u(l-7,0),u(0,l-7),O(),p(),b(y,Q),r>=7&&g(y),a==null&&(a=X(r,s,c)),w(a,Q)},u=function(y,Q){for(let S=-1;S<=7;S+=1)if(!(y+S<=-1||l<=y+S))for(let $=-1;$<=7;$+=1)Q+$<=-1||l<=Q+$||(0<=S&&S<=6&&($==0||$==6)||0<=$&&$<=6&&(S==0||S==6)||2<=S&&S<=4&&2<=$&&$<=4?o[y+S][Q+$]=!0:o[y+S][Q+$]=!1)},d=function(){let y=0,Q=0;for(let S=0;S<8;S+=1){f(!0,S);let $=hn.getLostPoint(h);(S==0||y>$)&&(y=$,Q=S)}return Q},p=function(){for(let y=8;y<l-8;y+=1)o[y][6]==null&&(o[y][6]=y%2==0);for(let y=8;y<l-8;y+=1)o[6][y]==null&&(o[6][y]=y%2==0)},O=function(){let y=hn.getPatternPosition(r);for(let Q=0;Q<y.length;Q+=1)for(let S=0;S<y.length;S+=1){let $=y[Q],k=y[S];if(o[$][k]==null)for(let P=-2;P<=2;P+=1)for(let E=-2;E<=2;E+=1)P==-2||P==2||E==-2||E==2||P==0&&E==0?o[$+P][k+E]=!0:o[$+P][k+E]=!1}},g=function(y){let Q=hn.getBCHTypeNumber(r);for(let S=0;S<18;S+=1){let $=!y&&(Q>>S&1)==1;o[Math.floor(S/3)][S%3+l-8-3]=$}for(let S=0;S<18;S+=1){let $=!y&&(Q>>S&1)==1;o[S%3+l-8-3][Math.floor(S/3)]=$}},b=function(y,Q){let S=s<<3|Q,$=hn.getBCHTypeInfo(S);for(let k=0;k<15;k+=1){let P=!y&&($>>k&1)==1;k<6?o[k][8]=P:k<8?o[k+1][8]=P:o[l-15+k][8]=P}for(let k=0;k<15;k+=1){let P=!y&&($>>k&1)==1;k<8?o[8][l-k-1]=P:k<9?o[8][15-k-1+1]=P:o[8][15-k-1]=P}o[l-8][8]=!y},w=function(y,Q){let S=-1,$=l-1,k=7,P=0,E=hn.getMaskFunction(Q);for(let D=l-1;D>0;D-=2)for(D==6&&(D-=1);;){for(let V=0;V<2;V+=1)if(o[$][D-V]==null){let N=!1;P<y.length&&(N=(y[P]>>>k&1)==1),E($,D-V)&&(N=!N),o[$][D-V]=N,k-=1,k==-1&&(P+=1,k=7)}if($+=S,$<0||l<=$){$-=S,S=-S;break}}},x=function(y,Q){let S=0,$=0,k=0,P=new Array(Q.length),E=new Array(Q.length);for(let j=0;j<Q.length;j+=1){let H=Q[j].dataCount,ie=Q[j].totalCount-H;$=Math.max($,H),k=Math.max(k,ie),P[j]=new Array(H);for(let ye=0;ye<P[j].length;ye+=1)P[j][ye]=255&y.getBuffer()[ye+S];S+=H;let je=hn.getErrorCorrectPolynomial(ie),Ne=rl(P[j],je.getLength()-1).mod(je);E[j]=new Array(je.getLength()-1);for(let ye=0;ye<E[j].length;ye+=1){let Ft=ye+Ne.getLength()-E[j].length;E[j][ye]=Ft>=0?Ne.getAt(Ft):0}}let D=0;for(let j=0;j<Q.length;j+=1)D+=Q[j].totalCount;let V=new Array(D),N=0;for(let j=0;j<$;j+=1)for(let H=0;H<Q.length;H+=1)j<P[H].length&&(V[N]=P[H][j],N+=1);for(let j=0;j<k;j+=1)for(let H=0;H<Q.length;H+=1)j<E[H].length&&(V[N]=E[H][j],N+=1);return V},X=function(y,Q,S){let $=bw.getRSBlocks(y,Q),k=yw();for(let E=0;E<S.length;E+=1){let D=S[E];k.put(D.getMode(),4),k.put(D.getLength(),hn.getLengthInBits(D.getMode(),y)),D.write(k)}let P=0;for(let E=0;E<$.length;E+=1)P+=$[E].dataCount;if(k.getLengthInBits()>P*8)throw"code length overflow. ("+k.getLengthInBits()+">"+P*8+")";for(k.getLengthInBits()+4<=P*8&&k.put(0,4);k.getLengthInBits()%8!=0;)k.putBit(!1);for(;!(k.getLengthInBits()>=P*8||(k.put(236,8),k.getLengthInBits()>=P*8));)k.put(17,8);return x(k,$)};h.addData=function(y,Q){Q=Q||"Byte";let S=null;switch(Q){case"Numeric":S=tR(y);break;case"Alphanumeric":S=iR(y);break;case"Byte":S=nR(y);break;case"Kanji":S=rR(y);break;default:throw"mode:"+Q}c.push(S),a=null},h.isDark=function(y,Q){if(y<0||l<=y||Q<0||l<=Q)throw y+","+Q;return o[y][Q]},h.getModuleCount=function(){return l},h.make=function(){if(r<1){let y=1;for(;y<40;y++){let Q=bw.getRSBlocks(y,s),S=yw();for(let k=0;k<c.length;k++){let P=c[k];S.put(P.getMode(),4),S.put(P.getLength(),hn.getLengthInBits(P.getMode(),y)),P.write(S)}let $=0;for(let k=0;k<Q.length;k++)$+=Q[k].dataCount;if(S.getLengthInBits()<=$*8)break}r=y}f(!1,d())},h.createTableTag=function(y,Q){y=y||2,Q=typeof Q>"u"?y*4:Q;let S="";S+='<table style="',S+=" border-width: 0px; border-style: none;",S+=" border-collapse: collapse;",S+=" padding: 0px; margin: "+Q+"px;",S+='">',S+="<tbody>";for(let $=0;$<h.getModuleCount();$+=1){S+="<tr>";for(let k=0;k<h.getModuleCount();k+=1)S+='<td style="',S+=" border-width: 0px; border-style: none;",S+=" border-collapse: collapse;",S+=" padding: 0px; margin: 0px;",S+=" width: "+y+"px;",S+=" height: "+y+"px;",S+=" background-color: ",S+=h.isDark($,k)?"#000000":"#ffffff",S+=";",S+='"/>';S+="</tr>"}return S+="</tbody>",S+="</table>",S},h.createSvgTag=function(y,Q,S,$){let k={};typeof arguments[0]=="object"&&(k=arguments[0],y=k.cellSize,Q=k.margin,S=k.alt,$=k.title),y=y||2,Q=typeof Q>"u"?y*4:Q,S=typeof S=="string"?{text:S}:S||{},S.text=S.text||null,S.id=S.text?S.id||"qrcode-description":null,$=typeof $=="string"?{text:$}:$||{},$.text=$.text||null,$.id=$.text?$.id||"qrcode-title":null;let P=h.getModuleCount()*y+Q*2,E,D,V,N,j="",H;for(H="l"+y+",0 0,"+y+" -"+y+",0 0,-"+y+"z ",j+='<svg version="1.1" xmlns="http://www.w3.org/2000/svg"',j+=k.scalable?"":' width="'+P+'px" height="'+P+'px"',j+=' viewBox="0 0 '+P+" "+P+'" ',j+=' preserveAspectRatio="xMinYMin meet"',j+=$.text||S.text?' role="img" aria-labelledby="'+C([$.id,S.id].join(" ").trim())+'"':"",j+=">",j+=$.text?'<title id="'+C($.id)+'">'+C($.text)+"":"",j+=S.text?''+C(S.text)+"":"",j+='',j+='"u"?y*4:Q;let S=h.getModuleCount()*y+Q*2,$=Q,k=S-Q;return aR(S,S,function(P,E){if($<=P&&P"u"?y*4:Q;let $=h.getModuleCount()*y+Q*2,k="";return k+="",k};let C=function(y){let Q="";for(let S=0;S":Q+=">";break;case"&":Q+="&";break;case'"':Q+=""";break;default:Q+=$;break}}return Q},T=function(y){y=typeof y>"u"?2:y;let S=h.getModuleCount()*1+y*2,$=y,k=S-y,P,E,D,V,N,j={"\u2588\u2588":"\u2588","\u2588 ":"\u2580"," \u2588":"\u2584"," ":" "},H={"\u2588\u2588":"\u2580","\u2588 ":"\u2580"," \u2588":" "," ":" "},ie="";for(P=0;P=k?H[N]:j[N];ie+=` +`}return S%2&&y>0?ie.substring(0,ie.length-S-1)+Array(S+1).join("\u2580"):ie.substring(0,ie.length-1)};return h.createASCII=function(y,Q){if(y=y||1,y<2)return T(Q);y-=1,Q=typeof Q>"u"?y*2:Q;let S=h.getModuleCount()*y+Q*2,$=Q,k=S-Q,P,E,D,V,N=Array(y+1).join("\u2588\u2588"),j=Array(y+1).join(" "),H="",ie="";for(P=0;P>>8),s.push(a&255)):s.push(n)}}return s}};var We={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},nl={L:1,M:0,Q:3,H:2},cn={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},hn=(function(){let i=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],e=1335,t=7973,n=21522,r={},s=function(o){let l=0;for(;o!=0;)l+=1,o>>>=1;return l};return r.getBCHTypeInfo=function(o){let l=o<<10;for(;s(l)-s(e)>=0;)l^=e<=0;)l^=t<5&&(a+=3+d-5)}for(let f=0;f=256;)n-=255;return i[n]},t})(),rl=function(i,e){if(typeof i.length>"u")throw i.length+"/"+e;let t=(function(){let r=0;for(;r"u")throw"bad rs block @ typeNumber:"+r+"/errorCorrectionLevel:"+s;let l=o.length/3,a=[];for(let c=0;c>>7-n%8&1)==1},t.put=function(n,r){for(let s=0;s>>r-s-1&1)==1)},t.getLengthInBits=function(){return e},t.putBit=function(n){let r=Math.floor(e/8);i.length<=r&&i.push(0),n&&(i[r]|=128>>>e%8),e+=1},t},tR=function(i){let e=We.MODE_NUMBER,t=i,n={};n.getMode=function(){return e},n.getLength=function(o){return t.length},n.write=function(o){let l=t,a=0;for(;a+2>>8&255)*192+(c&255),o.put(c,13),a+=2}if(a>>8)},e.writeBytes=function(t,n,r){n=n||0,r=r||t.length;for(let s=0;s0&&(t+=","),t+=i[n];return t+="]",t},e},sR=function(){let i=0,e=0,t=0,n="",r={},s=function(l){n+=String.fromCharCode(o(l&63))},o=function(l){if(l<0)throw"n:"+l;if(l<26)return 65+l;if(l<52)return 97+(l-26);if(l<62)return 48+(l-52);if(l==62)return 43;if(l==63)return 47;throw"n:"+l};return r.writeByte=function(l){for(i=i<<8|l&255,e+=8,t+=1;e>=6;)s(i>>>e-6),e-=6},r.flush=function(){if(e>0&&(s(i<<6-e),i=0,e=0),t%3!=0){let l=3-t%3;for(let a=0;a=e.length){if(r==0)return-1;throw"unexpected end of file./"+r}let a=e.charAt(t);if(t+=1,a=="=")return r=0,-1;if(a.match(/^\s$/))continue;n=n<<6|o(a.charCodeAt(0)),r+=6}let l=n>>>r-8&255;return r-=8,l};let o=function(l){if(65<=l&&l<=90)return l-65;if(97<=l&&l<=122)return l-97+26;if(48<=l&&l<=57)return l-48+52;if(l==43)return 62;if(l==47)return 63;throw"c:"+l};return s},lR=function(i,e){let t=i,n=e,r=new Array(i*e),s={};s.setPixel=function(c,h,f){r[h*t+c]=f},s.write=function(c){c.writeString("GIF87a"),c.writeShort(t),c.writeShort(n),c.writeByte(128),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(255),c.writeByte(255),c.writeByte(255),c.writeString(","),c.writeShort(0),c.writeShort(0),c.writeShort(t),c.writeShort(n),c.writeByte(0);let h=2,f=l(h);c.writeByte(h);let u=0;for(;f.length-u>255;)c.writeByte(255),c.writeBytes(f,u,255),u+=255;c.writeByte(f.length-u),c.writeBytes(f,u,f.length-u),c.writeByte(0),c.writeString(";")};let o=function(c){let h=c,f=0,u=0,d={};return d.write=function(p,O){if(p>>>O)throw"length over";for(;f+O>=8;)h.writeByte(255&(p<>>=8-f,u=0,f=0;u=p<0&&h.writeByte(u)},d},l=function(c){let h=1< Date: Wed, 14 Oct 2026 20:49:32 +0100 Subject: [PATCH 003/165] docs: decide how the brief session's editor bundle is vendored Assisted-by: Claude:claude-opus-5-5 --- ...he-brief-session-s-browser-editor-ships.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .abcd/development/decisions/adrs/2610141949110653-the-brief-session-s-browser-editor-ships.md diff --git a/.abcd/development/decisions/adrs/2610141949110653-the-brief-session-s-browser-editor-ships.md b/.abcd/development/decisions/adrs/2610141949110653-the-brief-session-s-browser-editor-ships.md new file mode 100644 index 0000000000..5038851191 --- /dev/null +++ b/.abcd/development/decisions/adrs/2610141949110653-the-brief-session-s-browser-editor-ships.md @@ -0,0 +1,41 @@ +--- +id: adr-2610141949110653 +slug: the-brief-session-s-browser-editor-ships +status: accepted +date: 2026-10-14 +supersedes: null +superseded_by: null +related_intents: [itd-2610091016262335] +related_rfcs: [] +related_adrs: [adr-47, adr-2610091016270711] +--- + +# ADR-2610141949110653: The brief session's browser editor ships as a vendored bundle the binary embeds + +## Context + +itd-2610091016262335 has a team editing its brief live in the browser. Live co-editing of raw Markdown needs a CRDT and an editor. The research note 2026-10-14-live-co-editing-to-one-pull-request.md picked Yjs v13 with CodeMirror 6 through y-codemirror.next: the shared text is the Markdown file byte for byte, and these are the most-used bindings. Writing either library from scratch is out of proportion to the intent. + +adr-47 keeps a Node toolchain out of this repository. No `package.json`, lockfile or npm step runs in CI or in a build. The site build depends only on the binary. On 2026-10-14 the product thinker signed off on vendoring the two libraries. They also ruled that a technical choice grounded in state-of-the-art practice needs no further sign-off. + +## Decision + +We will ship the editor's third-party code as one prebuilt ES module, `internal/surface/session/web/vendor/collab.js`. It is committed beside its build entry module (`collab.entry.js`), a `PROVENANCE.json` and a `LICENSES.txt`, and the binary embeds it. + +1. **The bundle is built outside the repository.** The build installs exact versions with install scripts off. It resolves only releases published at least two weeks before the build, and bundles with a pinned esbuild. The build is reproducible: two runs give the same bytes. +2. **Provenance is committed and checked.** `PROVENANCE.json` names every bundled package, its version, its npm integrity hash and its tarball URL. It also records the bundler and command, the entry module's SHA-256 and the bundle's SHA-256. A Go test fails when the committed bytes no longer match the recorded hashes. +3. **Licences travel with it.** `LICENSES.txt` carries every bundled package's licence, all MIT. Where a package ships no licence file, its source copyright header is used instead. +4. **A bump repeats the build in full.** It uses a new cut-off date and rewrites all four files in one change. Nothing in the repository runs npm. + +## Alternatives Considered + +- **A committed `package.json` and lockfile, built in CI.** Lockfile integrity is the stronger supply-chain control, but it brings the Node toolchain adr-47 rules out, and a CI build step on every release. Rejected for now. A later ADR may revisit adr-47 for this. +- **Loading the libraries from a CDN at runtime.** This adds no repository weight, but every session would then send a request to a third party abcd chose. The page's integrity would also depend on the CDN's availability. Rejected. +- **A plain textarea with hand-written merging.** This needs no third-party code, but merging concurrent edits correctly is the CRDT problem itself. Rejected. +- **The vendored, provenance-pinned bundle (chosen).** No toolchain enters the repository. The bytes are auditable and rebuildable. The cost is a committed 720 KB minified file and a manual bump. + +## Consequences + +- The binary grows by the bundle's size, about 720 KB, or 250 KB gzipped. +- A security fix in a bundled package reaches users only through a deliberate rebuild and a release. Dependabot cannot see the bundle, so the bump is a facilitator's task. The provenance file makes the bump auditable. +- Reviewers cannot read a minified bundle. They review the entry module, the provenance and the hash test instead, and anyone can rebuild the bundle and compare hashes. From 228b7312a8136f1da34883e10a823aa29f1255d6 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:53:59 +0100 Subject: [PATCH 004/165] docs: admit the brief session's relay as abcd's second listener Amends adr-2610032150581128 in the open, as its own rule requires, with the terms the session relay runs under: started on purpose, loopback unless an address is chosen, reached on a person's machine through Tailscale Serve and never Funnel, admission by join code and GitHub's answer, and no project content at rest. Brief invariant 7 and adr-2610091016270711's consequences now say the same. Assisted-by: Claude:claude-opus-5-5 --- .../brief/02-constraints/03-invariants.md | 8 +++++++- ...581128-abcd-is-reachable-on-a-network-only.md | 16 ++++++++++++++-- ...1-who-may-change-an-abcd-managed-project-s.md | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.abcd/development/brief/02-constraints/03-invariants.md b/.abcd/development/brief/02-constraints/03-invariants.md index bd611289cd..994de42f82 100644 --- a/.abcd/development/brief/02-constraints/03-invariants.md +++ b/.abcd/development/brief/02-constraints/03-invariants.md @@ -34,7 +34,13 @@ The following are non-negotiable invariants — any architectural choice that vi which refines adr-38 without superseding it). One package alone opens a listener, held by `TestOnlyTheDashboardOpensAListener` (`internal/surface/dashboard`); a later listener is admitted only by - amending that record in the open. + amending that record in the open. Its amendment of 2026-10-14 admits one + second listener, the brief session's relay (`internal/surface/session`). + The relay is started on purpose and listens on loopback unless an address + is chosen on that run. On a person's machine it is reached through + Tailscale Serve, never Funnel. It admits people by the host's join code + and by GitHub's own answer on a presented token, and it holds no project + content at rest. 8. **Acceptance discipline applies uniformly** — every intent's press release is followed by a `## Acceptance Criteria` block in Given-When-Then format (per itd-1). Every brief phase has an `## Acceptance` block in the same format. The format is uniform across the boundary; the *home* differs to match the nature of the work. See [`01-product/03-mental-model.md`](../01-product/03-mental-model.md). diff --git a/.abcd/development/decisions/adrs/2610032150581128-abcd-is-reachable-on-a-network-only.md b/.abcd/development/decisions/adrs/2610032150581128-abcd-is-reachable-on-a-network-only.md index 4dcf5fcbbe..600012eb68 100644 --- a/.abcd/development/decisions/adrs/2610032150581128-abcd-is-reachable-on-a-network-only.md +++ b/.abcd/development/decisions/adrs/2610032150581128-abcd-is-reachable-on-a-network-only.md @@ -5,9 +5,9 @@ status: accepted date: 2026-10-03 supersedes: null superseded_by: null -related_intents: [itd-2610032150577708, itd-2610032150580455, itd-2610040740108331] +related_intents: [itd-2610032150577708, itd-2610032150580455, itd-2610040740108331, itd-2610091016262335] related_rfcs: [] -related_adrs: [adr-38, adr-2609091248200336] +related_adrs: [adr-38, adr-2609091248200336, adr-2610091016270711] --- # ADR-2610032150581128: abcd is reachable on a network only through the dashboard, only while the person runs it, and only for people the person lets in @@ -49,6 +49,18 @@ _From the security review of 2026-10-03 (Fable 5.1, verdict "block as drafted" u - On the local network it runs in the foreground, stops itself when idle and after a hard limit, and says so; running unattended is allowed only behind Tailscale Serve (never Funnel), where pairing still applies and the Tailscale identity is a label, never a key. - The start line says the page is only as private as the network: anyone who can read the Wi-Fi can read it. The person confirms on each run that the network is their own. +### Amendment of 2026-10-14: the brief session's relay is the second listener + +Amended in the open, as the second bullet above requires, by the change that lands `abcd session serve` (itd-2610091016262335, spc-2610141709430146). On 2026-10-14 the product thinker ruled that a team's live brief session runs on the team's own machine over Tailscale or on the team's own relay ("we offer tailscale and the rascals pattern as build-in defaults"). They also signed off on the relay living inside the abcd binary. Both rulings are recorded in itd-2610091016262335's Decisions and in adr-2610091016270711. That makes a second listener the product thinker's decision in substance. This amendment writes down the terms it runs under, which the facilitator settled on delegation: + +- **It is the session relay and nothing else.** `internal/surface/session` is the second package that may open a listener, beside `internal/surface/dashboard`. `TestOnlyTheDashboardOpensAListener` becomes a two-entry allowlist, and each entry cites this record. +- **It listens only while someone has started it on purpose,** on loopback unless an address is chosen on that run. A non-loopback address (`--listen`) is the second, explicit choice the security review requires. It is meant for the team's own relay deployment, which runs behind its host's proxy (`--behind-proxy`). +- **On a person's own machine, it is reached through Tailscale Serve, on the tailnet only, and never through Funnel.** Decision 21's objection to Serve (that Serve hides which device connects) does not bind here, because the relay never admits anyone by device identity. Admission is by the host's join code, and team membership is by GitHub's own answer on the presented token. Funnel would expose the person's computer to the internet, so a guest outside the tailnet joins through the team's relay instead. +- **It holds no project content at rest.** A session's edits are opaque bytes in memory, dropped when the host closes the session or it goes idle. Nothing is written to disk, and no token, secret or brief text is logged. +- **The rest of this record holds for it unchanged:** limits on message size, connections and sessions; refusal of unexpected origins; and nothing third-party served from the person's computer. + +This refines the dashboard's rule and does not relax it. The dashboard stays Tailscale-address-only and never uses Serve or Funnel. + ## Alternatives Considered - Never listen, and publish a static site instead (the superseded itd-139's route), or rebuild the existing record site on change and serve the folder: rejected by the record review of 2026-10-03. The site build needs a composition only `site setup` plants, takes minutes, and rewrites a committed file on every build; it shows the facilitator's tiles and record ids, not what waits on the product thinker; and serving the folder still needs a listener and this rule. diff --git a/.abcd/development/decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md b/.abcd/development/decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md index d2df5f185f..d4352db354 100644 --- a/.abcd/development/decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md +++ b/.abcd/development/decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md @@ -57,4 +57,4 @@ Decided by the product thinker's rulings of 2026-10-14, recorded verbatim in itd - Each team does a one-off setup: deploying the relay or joining the host's Tailscale network, and registering its own GitHub sign-in app. Keeping that relay and its secret safe is the team's responsibility, not abcd's. - Anyone the host gives the code to sees the brief, a private one included; admission of guests is the host's call. - The session needs per-person change tracking and a suggestion mode for guests. -- abcd adds no network surface of its own: its only network surface stays the local dashboard (adr-2610032150581128), and the hosted page at abcdesign.app is static. +- abcd runs no network surface of its own anywhere. The relay is a verb in the abcd binary that the team starts on its own machine or deploys to its own account, admitted as the second listener by the 2026-10-14 amendment of adr-2610032150581128. The hosted page at abcdesign.app is static. From 96e9c21760af8246e5b9da2aa0ffaf169dd28101 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:54:09 +0100 Subject: [PATCH 005/165] docs: keep the session's host route on the tailnet Assisted-by: Claude:claude-opus-5-5 --- ...pc-2610141709430146-the-product-thinker-and-the-technical.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index 6be5148351..676ad3ee1b 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -17,7 +17,7 @@ A team opens its brief at abcdesign.app///brief/ during a call and - a health check at `/healthz` The relay reaches the team by one of two routes: -- **The host's own machine.** The host exposes the relay with `tailscale serve`, which reaches teammates on the tailnet, or `tailscale funnel`, which reaches people outside it. Both give the browser the https address it needs. +- **The host's own machine.** The host exposes the relay with `tailscale serve`, which reaches teammates on the tailnet and gives the browser the https address it needs. Funnel is never used, because it would put the person's computer on the internet. A guest outside the tailnet joins through the team's relay instead (adr-2610032150581128, amendment of 2026-10-14). - **The team's own relay.** `abcd session blueprint` writes a Render Blueprint and a Dockerfile under `.abcd/relay/`. The Dockerfile fetches the released abcd binary for linux/amd64 and checks it against the release's checksum before running the same verb. One deploy serves all of the team's sessions. The relay holds: From 13316e4b2d3e96203d082a9c838db33bd90290cd Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:58:36 +0100 Subject: [PATCH 006/165] feat: add the brief session relay's core Rooms opened by a verified team member, six-character join codes from the 31-symbol alphabet, guests and members, the update log replayed to newcomers, guest updates refused, suggestions routed to members stamped with the guest's name, the roster with credited Yjs client ids, wrong codes limited per address, size and count limits, and purge on host close or idle. In memory only; no transport, no stdout. Assisted-by: Claude:claude-opus-5-5 --- internal/core/session/codes.go | 150 ++++++ internal/core/session/relay.go | 766 ++++++++++++++++++++++++++++ internal/core/session/relay_test.go | 648 +++++++++++++++++++++++ 3 files changed, 1564 insertions(+) create mode 100644 internal/core/session/codes.go create mode 100644 internal/core/session/relay.go create mode 100644 internal/core/session/relay_test.go diff --git a/internal/core/session/codes.go b/internal/core/session/codes.go new file mode 100644 index 0000000000..6b2d1ffa6c --- /dev/null +++ b/internal/core/session/codes.go @@ -0,0 +1,150 @@ +package session + +import ( + "errors" + "io" + "net/netip" + "strings" + "time" +) + +// CodeAlphabet is the join code's 31 symbols: capital letters and digits +// without the ones read for each other (I, L, O, 0, 1), as the rascals game +// server's room and link codes use. Six of them make about 887 million codes. +const CodeAlphabet = "ABCDEFGHJKMNPQRSTUVWXYZ23456789" + +// codeLength is the join code's length. +const codeLength = 6 + +// maxCodeInput caps what a join code may be typed as before it is read: the +// six symbols with a separator or two. +const maxCodeInput = 12 + +// newCode draws a join code no live session holds, from r.rand. A byte at or +// above 248 (eight times 31) is drawn again rather than folded, so every +// symbol is equally likely. Called under the lock. +func (r *Relay) newCode() (string, error) { + const limit = byte(len(CodeAlphabet) * 8) + for attempt := 0; attempt < 16; attempt++ { + var b strings.Builder + buf := make([]byte, 1) + for b.Len() < codeLength { + if _, err := io.ReadFull(r.rand, buf); err != nil { + return "", err + } + if buf[0] >= limit { + continue + } + b.WriteByte(CodeAlphabet[int(buf[0])%len(CodeAlphabet)]) + } + if _, taken := r.rooms[b.String()]; !taken { + return b.String(), nil + } + } + return "", errors.New("no free join code") +} + +// NormalizeCode reads a join code as a person types it: case ignored, spaces +// and dashes dropped. Anything longer than a code with separators, or with a +// symbol outside the alphabet once read, is no code (""). +func NormalizeCode(s string) string { + if len(s) > maxCodeInput { + return "" + } + var b strings.Builder + for _, r := range strings.ToUpper(s) { + switch { + case r == ' ' || r == '-': + continue + case strings.ContainsRune(CodeAlphabet, r): + b.WriteRune(r) + default: + return "" + } + } + if b.Len() != codeLength { + return "" + } + return b.String() +} + +// AddressKey is the key the failed-join limit counts by: an IPv4 address, or +// an IPv6 /64 network, since one home or phone usually holds a whole /64 and +// counting single IPv6 addresses would let one visitor rotate through +// millions (the rascals server's addressKey). An address that does not parse +// is its own key. +func AddressKey(addr string) string { + a, err := netip.ParseAddr(strings.TrimSpace(addr)) + if err != nil { + return strings.TrimSpace(addr) + } + a = a.Unmap().WithZone("") + if a.Is4() { + return a.String() + } + p, err := a.Prefix(64) + if err != nil { + return a.String() + } + return p.String() +} + +// failedJoins counts wrong codes per address within a window, remembering at +// most max addresses (the oldest are forgotten first). +type failedJoins struct { + limit int + window time.Duration + max int + byAddr map[string][]time.Time + order []string // insertion order, oldest first +} + +func newFailedJoins(limit int, window time.Duration, max int) *failedJoins { + return &failedJoins{limit: limit, window: window, max: max, byAddr: map[string][]time.Time{}} +} + +// recent is addr's wrong codes inside the window. +func (f *failedJoins) recent(addr string, now time.Time) []time.Time { + var out []time.Time + for _, t := range f.byAddr[addr] { + if now.Sub(t) < f.window { + out = append(out, t) + } + } + return out +} + +// blocked reports whether addr has used up its wrong codes. +func (f *failedJoins) blocked(addr string, now time.Time) bool { + return len(f.recent(addr, now)) >= f.limit +} + +// note counts one wrong code from addr and returns its count in the window. +func (f *failedJoins) note(addr string, now time.Time) int { + list := append(f.recent(addr, now), now) + if _, seen := f.byAddr[addr]; !seen { + f.order = append(f.order, addr) + } + f.byAddr[addr] = list + if len(f.byAddr) > f.max { + f.sweep(now) + for len(f.byAddr) > f.max && len(f.order) > 0 { + delete(f.byAddr, f.order[0]) + f.order = f.order[1:] + } + } + return len(list) +} + +// sweep forgets every address with no wrong code inside the window. +func (f *failedJoins) sweep(now time.Time) { + kept := f.order[:0] + for _, a := range f.order { + if len(f.recent(a, now)) == 0 { + delete(f.byAddr, a) + continue + } + kept = append(kept, a) + } + f.order = kept +} diff --git a/internal/core/session/relay.go b/internal/core/session/relay.go new file mode 100644 index 0000000000..49d17f11e5 --- /dev/null +++ b/internal/core/session/relay.go @@ -0,0 +1,766 @@ +// Package session is the brief session's relay, without a transport +// (itd-2610091016262335, spc-2610141709430146 step 1): the rooms a host opens, +// the join codes that reach them, who is a guest and who a team member, the +// log of opaque edit updates each room replays to a newcomer, a guest's +// suggestions routed to the members, the roster, and the limits that bound all +// of it. +// +// It never writes to stdout, opens no file and knows no transport: the front +// door (internal/surface/session) hands it one Frame at a time through a Conn +// and gives it a Peer to answer on. It holds everything in memory, and a room +// is dropped whole when its host closes it or when nobody has been connected +// for the idle limit (adr-2610032150581128, as amended 2026-10-14: no project +// content at rest). +// +// The edit updates and awareness messages are Yjs's, carried as opaque bytes +// behind a one-byte tag; the relay never parses them, nor the brief they +// carry. A GitHub token presented to verify is handed to the Verifier and not +// kept: only the login and the answer are. +package session + +import ( + "context" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "regexp" + "strconv" + "strings" + "sync" + "time" + "unicode" +) + +// The binary frame tags: the first byte of every binary frame. +const ( + // TagUpdate is a Yjs document update: logged and forwarded, and accepted + // from team members only. + TagUpdate byte = 0 + // TagAwareness is a Yjs awareness message (cursors and selections): + // forwarded from anyone, never logged. + TagAwareness byte = 1 +) + +// Frame is one WebSocket message: a JSON control message in a text frame, or +// a tagged Yjs message in a binary frame. +type Frame struct { + Binary bool + Data []byte +} + +// Peer is the transport's handle on one connection. +type Peer interface { + // Send queues f without blocking. False means the connection cannot take + // it (its queue is full), and the relay drops the connection. + Send(f Frame) bool + // Close ends the connection; policy marks a refusal (WebSocket 1008) + // rather than a normal end. It must not call back into the relay. + Close(policy bool, reason string) +} + +// Verifier asks GitHub who a token belongs to and whether that login may push +// to repo. The token is not kept by the relay after the call returns. +type Verifier interface { + Verify(ctx context.Context, token, repo string) (login string, push bool, err error) +} + +// Limits bound everything the relay holds. +type Limits struct { + // MaxMessageBytes caps one frame, text or binary. + MaxMessageBytes int + // MaxLogBytes caps one session's update log. + MaxLogBytes int + // MaxParticipants caps the connections in one session. + MaxParticipants int + // MaxSessions caps the sessions one relay holds at once. + MaxSessions int + // IdleAfter is how long a session with nobody connected is kept. + IdleAfter time.Duration + // MaxFailedJoins wrong codes from one address within FailedJoinWindow + // shut that address out of joining until the window passes. + MaxFailedJoins int + FailedJoinWindow time.Duration + // MaxTrackedAddrs caps the addresses the failed-join count remembers. + MaxTrackedAddrs int + // MaxVerifies caps the verify attempts of one connection, so the relay + // cannot be used to hammer GitHub. + MaxVerifies int + // MaxCredits caps the Yjs client ids one session credits to members. + MaxCredits int +} + +// DefaultLimits are the relay's limits unless a caller sets others. Sixteen +// sessions of at most 8 MiB of log each keep a relay inside a free host's +// memory. +func DefaultLimits() Limits { + return Limits{ + MaxMessageBytes: 256 << 10, + MaxLogBytes: 8 << 20, + MaxParticipants: 32, + MaxSessions: 16, + IdleAfter: 30 * time.Minute, + MaxFailedJoins: 20, + FailedJoinWindow: 10 * time.Minute, + MaxTrackedAddrs: 10000, + MaxVerifies: 5, + MaxCredits: 512, + } +} + +// Options configure a Relay. A zero Now is time.Now and a nil Rand is +// crypto/rand; a zero limit takes its default. +type Options struct { + Limits Limits + Verifier Verifier + Now func() time.Time + Rand io.Reader +} + +// Stats is what the relay holds, in counts: after a session is purged, none of +// it counts towards any of these. +type Stats struct { + Sessions int + Codes int + Participants int + LogBytes int + Credits int +} + +// The two roles. +const ( + roleGuest = "guest" + roleMember = "member" +) + +// Relay holds the sessions. One mutex guards all of it; Peer.Send never +// blocks, so nothing waits on a connection while holding it. +type Relay struct { + mu sync.Mutex + limits Limits + verifier Verifier + now func() time.Time + rand io.Reader + rooms map[string]*room // by join code + failed *failedJoins + nextConn int +} + +// New builds a relay. +func New(o Options) *Relay { + l := o.Limits + d := DefaultLimits() + fill := func(v *int, def int) { + if *v <= 0 { + *v = def + } + } + fill(&l.MaxMessageBytes, d.MaxMessageBytes) + fill(&l.MaxLogBytes, d.MaxLogBytes) + fill(&l.MaxParticipants, d.MaxParticipants) + fill(&l.MaxSessions, d.MaxSessions) + fill(&l.MaxFailedJoins, d.MaxFailedJoins) + fill(&l.MaxTrackedAddrs, d.MaxTrackedAddrs) + fill(&l.MaxVerifies, d.MaxVerifies) + fill(&l.MaxCredits, d.MaxCredits) + if l.IdleAfter <= 0 { + l.IdleAfter = d.IdleAfter + } + if l.FailedJoinWindow <= 0 { + l.FailedJoinWindow = d.FailedJoinWindow + } + r := &Relay{limits: l, verifier: o.Verifier, now: o.Now, rand: o.Rand, rooms: map[string]*room{}} + if r.now == nil { + r.now = time.Now + } + if r.rand == nil { + r.rand = rand.Reader + } + r.failed = newFailedJoins(l.MaxFailedJoins, l.FailedJoinWindow, l.MaxTrackedAddrs) + return r +} + +// Limits reports the limits in force, defaults filled in. +func (r *Relay) Limits() Limits { return r.limits } + +// room is one session. +type room struct { + repo string // as the host named it + repoKey string // lower-cased, for comparison + code string + hostLogin string + conns []*Conn // in join order + log [][]byte + logBytes int + credits map[uint32]string // Yjs client id -> member login + emptySince time.Time + suggests int +} + +// Conn is one connection's state. Its methods are called by one goroutine per +// connection (the transport's read loop). +type Conn struct { + r *Relay + id string + peer Peer + addr string // the failed-join key: an IPv4 address or an IPv6 /64 + room *room + role string + name string + login string // the verified GitHub login, if any + memberOf string // the repository key the verified login may push to + verifies int + left bool +} + +// Connect admits a new connection from addr (the client's address as the +// transport judged it). +func (r *Relay) Connect(p Peer, addr string) *Conn { + r.mu.Lock() + defer r.mu.Unlock() + r.nextConn++ + return &Conn{r: r, id: "p" + strconv.Itoa(r.nextConn), peer: p, addr: AddressKey(addr)} +} + +// inbound is every field a control message may carry. +type inbound struct { + Type string `json:"type"` + Repo string `json:"repo"` + Code string `json:"code"` + Name string `json:"name"` + Token string `json:"token"` + Client json.Number `json:"client"` + Body json.RawMessage `json:"body"` +} + +// Receive handles one frame from the connection. +func (c *Conn) Receive(ctx context.Context, f Frame) { + r := c.r + if len(f.Data) > r.limits.MaxMessageBytes { + r.mu.Lock() + c.fail("too-big", fmt.Sprintf("a message is at most %d bytes", r.limits.MaxMessageBytes)) + r.mu.Unlock() + return + } + if f.Binary { + r.mu.Lock() + defer r.mu.Unlock() + c.binary(f.Data) + return + } + var m inbound + dec := json.NewDecoder(strings.NewReader(string(f.Data))) + dec.UseNumber() + if err := dec.Decode(&m); err != nil || dec.More() { + r.mu.Lock() + c.fail("bad-message", "a control message is one JSON object") + r.mu.Unlock() + return + } + if m.Type == "verify" { + c.verify(ctx, m.Token, m.Repo) + return + } + r.mu.Lock() + defer r.mu.Unlock() + if c.left { + return + } + switch m.Type { + case "host": + c.host(m.Repo) + case "join": + c.join(m.Code, m.Name) + case "suggest": + c.suggest(m.Body) + case "register": + c.register(m.Client) + case "rotate": + c.rotate() + case "close": + c.closeRoom() + default: + c.fail("unknown-message", "the relay does not know this message") + } +} + +// Leave removes the connection; the transport calls it when the connection +// ends, and it may be called more than once. +func (c *Conn) Leave() { + c.r.mu.Lock() + defer c.r.mu.Unlock() + c.leave() +} + +// leave is Leave under the lock. +func (c *Conn) leave() { + if c.left { + return + } + c.left = true + rm := c.room + c.room = nil + if rm == nil { + return + } + for i, o := range rm.conns { + if o == c { + rm.conns = append(rm.conns[:i], rm.conns[i+1:]...) + break + } + } + if len(rm.conns) == 0 { + rm.emptySince = c.r.now() + return + } + c.r.broadcastRoster(rm) +} + +// send queues one message on c; a peer that cannot take it is dropped. +func (c *Conn) send(f Frame) { + if c.left { + return + } + if !c.peer.Send(f) { + c.leave() + c.peer.Close(true, "too slow") + } +} + +func (c *Conn) sendJSON(v any) { + data, err := json.Marshal(v) + if err != nil { + return + } + c.send(Frame{Data: data}) +} + +// fail answers c with an error message. Its text is fixed per code and never +// echoes what the client sent. +func (c *Conn) fail(code, text string) { + c.sendJSON(map[string]string{"type": "error", "code": code, "message": text}) +} + +// binary handles a tagged Yjs frame, under the lock. +func (c *Conn) binary(data []byte) { + if c.left { + return + } + rm := c.room + switch { + case len(data) == 0: + c.fail("empty-frame", "a binary frame starts with its tag") + case rm == nil: + c.fail("not-in-session", "join or host a session first") + case data[0] == TagUpdate: + if c.role != roleMember { + c.fail("read-only", "a guest suggests changes; only a team member edits") + return + } + if rm.logBytes+len(data) > c.r.limits.MaxLogBytes { + c.fail("log-full", "this session's log is full; the host can close it and open a new one") + return + } + entry := append([]byte(nil), data...) + rm.log = append(rm.log, entry) + rm.logBytes += len(entry) + c.r.forward(rm, c, entry) + case data[0] == TagAwareness: + c.r.forward(rm, c, append([]byte(nil), data...)) + default: + c.fail("unknown-tag", "the relay knows update (0) and awareness (1) frames") + } +} + +// forward sends a binary frame to everyone in rm but from. +func (r *Relay) forward(rm *room, from *Conn, data []byte) { + for _, o := range append([]*Conn(nil), rm.conns...) { + if o != from { + o.send(Frame{Binary: true, Data: data}) + } + } +} + +// repoRe is a GitHub owner/name: an owner of letters, digits and hyphens, a +// name of letters, digits, '.', '_' and '-'. +var repoRe = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$`) + +// ValidRepo reports whether s is an owner/name a URL path may carry. +func ValidRepo(s string) bool { + if !repoRe.MatchString(s) { + return false + } + name := s[strings.IndexByte(s, '/')+1:] + return name != "." && name != ".." +} + +// verify asks the Verifier about a token, outside the lock. +func (c *Conn) verify(ctx context.Context, token, repo string) { + r := c.r + r.mu.Lock() + if c.left { + r.mu.Unlock() + return + } + if c.role == roleMember { + c.fail("already-verified", "this connection is a team member already") + r.mu.Unlock() + return + } + if c.verifies >= r.limits.MaxVerifies { + c.fail("too-many-verifies", "too many sign-in attempts on this connection") + r.mu.Unlock() + return + } + if c.room != nil { + if repo != "" && !strings.EqualFold(repo, c.room.repo) { + c.fail("bad-repo", "a session verifies against its own repository") + r.mu.Unlock() + return + } + repo = c.room.repo + } + if !ValidRepo(repo) { + c.fail("bad-repo", "name the repository as owner/name") + r.mu.Unlock() + return + } + if token == "" || r.verifier == nil { + c.fail("verify-failed", "GitHub did not accept the sign-in") + r.mu.Unlock() + return + } + c.verifies++ + r.mu.Unlock() + + login, push, err := r.verifier.Verify(ctx, token, repo) + token = "" // dropped straight after the check + _ = token + + r.mu.Lock() + defer r.mu.Unlock() + if c.left { + return + } + if err != nil || login == "" { + c.fail("verify-failed", "GitHub did not accept the sign-in") + return + } + c.login = login + if push { + c.memberOf = strings.ToLower(repo) + } + member := false + if rm := c.room; rm != nil && push && c.memberOf == rm.repoKey { + c.role, c.name, member = roleMember, login, true + } else if c.room == nil { + member = push + } + c.sendJSON(map[string]any{"type": "verified", "login": login, "member": member}) + if c.room != nil { + r.broadcastRoster(c.room) + } +} + +// host opens a session for repo, which c must be a verified member of. +func (c *Conn) host(repo string) { + r := c.r + switch { + case c.room != nil: + c.fail("already-in-session", "this connection is in a session already") + return + case !ValidRepo(repo): + c.fail("bad-repo", "name the repository as owner/name") + return + case c.login == "": + c.fail("not-verified", "sign in with GitHub before hosting") + return + case c.memberOf != strings.ToLower(repo): + c.fail("not-a-member", "only someone who can change the repository hosts its session") + return + case len(r.rooms) >= r.limits.MaxSessions: + c.fail("too-many-sessions", "this relay holds as many sessions as it may") + return + } + code, err := r.newCode() + if err != nil { + c.fail("busy", "no join code could be drawn") + return + } + rm := &room{repo: repo, repoKey: strings.ToLower(repo), code: code, hostLogin: c.login, credits: map[uint32]string{}} + r.rooms[code] = rm + c.room, c.role, c.name = rm, roleMember, c.login + rm.conns = append(rm.conns, c) + c.sendJSON(map[string]any{"type": "hosted", "id": c.id, "code": code, "repo": repo}) + c.sendJSON(map[string]string{"type": "synced"}) + r.broadcastRoster(rm) +} + +// join admits c to the session code names. +func (c *Conn) join(code, name string) { + r := c.r + if c.room != nil { + c.fail("already-in-session", "this connection is in a session already") + return + } + now := r.now() + if r.failed.blocked(c.addr, now) { + c.fail("too-many-tries", "too many wrong codes from this address; try again later") + c.leave() + c.peer.Close(true, "too many wrong codes") + return + } + rm := r.rooms[NormalizeCode(code)] + if rm == nil { + c.fail("bad-code", "no session has that code") + if r.failed.note(c.addr, now) >= r.limits.MaxFailedJoins { + c.leave() + c.peer.Close(true, "too many wrong codes") + } + return + } + if len(rm.conns) >= r.limits.MaxParticipants { + c.fail("full", "this session has as many participants as it may") + return + } + c.room = rm + if c.login != "" && c.memberOf == rm.repoKey { + c.role, c.name = roleMember, c.login + } else { + c.role, c.name = roleGuest, CleanName(name) + } + rm.conns = append(rm.conns, c) + c.sendJSON(map[string]any{"type": "joined", "id": c.id, "role": c.role, "name": c.name, "repo": rm.repo, "host": c.isHost()}) + for _, u := range rm.log { + c.send(Frame{Binary: true, Data: u}) + } + c.sendJSON(map[string]string{"type": "synced"}) + r.broadcastRoster(rm) +} + +// isHost reports whether c may rotate and close: a member signed in as the +// login that opened the session, so a host whose page reloads keeps the role. +func (c *Conn) isHost() bool { + return c.room != nil && c.role == roleMember && c.login == c.room.hostLogin +} + +// suggest routes a guest's proposed change to the team members. +func (c *Conn) suggest(body json.RawMessage) { + rm := c.room + switch { + case rm == nil: + c.fail("not-in-session", "join or host a session first") + return + case c.role != roleGuest: + c.fail("not-a-guest", "a team member edits directly") + return + } + trimmed := strings.TrimSpace(string(body)) + var obj map[string]json.RawMessage + if !strings.HasPrefix(trimmed, "{") || json.Unmarshal(body, &obj) != nil { + c.fail("bad-suggestion", "a suggestion's body is one JSON object") + return + } + rm.suggests++ + id := "s" + strconv.Itoa(rm.suggests) + out := map[string]any{"type": "suggest", "id": id, "from": c.name, "from_id": c.id, "body": json.RawMessage(trimmed)} + delivered := 0 + for _, o := range append([]*Conn(nil), rm.conns...) { + if o.role == roleMember { + o.sendJSON(out) + delivered++ + } + } + c.sendJSON(map[string]any{"type": "suggested", "id": id, "delivered": delivered}) +} + +// register credits a Yjs client id to the member c is signed in as. +func (c *Conn) register(raw json.Number) { + rm := c.room + if rm == nil { + c.fail("not-in-session", "join or host a session first") + return + } + if c.role != roleMember { + c.fail("read-only", "only a team member's edits are credited") + return + } + id, err := strconv.ParseUint(string(raw), 10, 32) + if err != nil { + c.fail("bad-client", "a client id is a whole number from 0 to 4294967295") + return + } + cid := uint32(id) + if owner, ok := rm.credits[cid]; ok { + if owner != c.login { + c.fail("client-taken", "that client id is credited to someone else") + } + return + } + if len(rm.credits) >= c.r.limits.MaxCredits { + c.fail("too-many-clients", "this session credits as many client ids as it may") + return + } + rm.credits[cid] = c.login + c.r.broadcastRoster(rm) +} + +// rotate replaces the session's join code. +func (c *Conn) rotate() { + if !c.isHost() { + c.fail("not-host", "only the host does that") + return + } + rm := c.room + code, err := c.r.newCode() + if err != nil { + c.fail("busy", "no join code could be drawn") + return + } + delete(c.r.rooms, rm.code) + rm.code = code + c.r.rooms[code] = rm + for _, o := range append([]*Conn(nil), rm.conns...) { + if o.isHost() { + o.sendJSON(map[string]string{"type": "code", "code": code}) + } + } +} + +// closeRoom ends the session for everyone and drops all of it. +func (c *Conn) closeRoom() { + if !c.isHost() { + c.fail("not-host", "only the host does that") + return + } + rm := c.room + conns := append([]*Conn(nil), rm.conns...) + for _, o := range conns { + o.sendJSON(map[string]string{"type": "closed", "reason": "host"}) + } + for _, o := range conns { + o.left, o.room = true, nil + o.peer.Close(false, "session closed") + } + rm.conns = nil + c.r.purge(rm) +} + +// purge drops a room and everything it holds. +func (r *Relay) purge(rm *room) { + if r.rooms[rm.code] == rm { + delete(r.rooms, rm.code) + } + rm.log, rm.logBytes, rm.credits = nil, 0, nil +} + +// rosterEntry is one participant as the roster shows them. +type rosterEntry struct { + ID string `json:"id"` + Name string `json:"name"` + Login string `json:"login,omitempty"` + Role string `json:"role"` + Host bool `json:"host,omitempty"` +} + +// broadcastRoster sends rm's roster to everyone in it: the participants with +// their verified identities, and the Yjs client ids credited to each member. +func (r *Relay) broadcastRoster(rm *room) { + parts := make([]rosterEntry, 0, len(rm.conns)) + for _, o := range rm.conns { + parts = append(parts, rosterEntry{ID: o.id, Name: o.name, Login: o.login, Role: o.role, Host: o.isHost()}) + } + clients := make(map[string]string, len(rm.credits)) + for id, login := range rm.credits { + clients[strconv.FormatUint(uint64(id), 10)] = login + } + data, err := json.Marshal(map[string]any{"type": "roster", "participants": parts, "clients": clients}) + if err != nil { + return + } + for _, o := range append([]*Conn(nil), rm.conns...) { + o.send(Frame{Data: data}) + } +} + +// Sweep purges every session nobody has been connected to for the idle +// limit, and forgets addresses whose wrong codes have aged out. The transport +// calls it on a timer. +func (r *Relay) Sweep() { + r.mu.Lock() + defer r.mu.Unlock() + now := r.now() + for _, rm := range r.rooms { + if len(rm.conns) == 0 && now.Sub(rm.emptySince) >= r.limits.IdleAfter { + r.purge(rm) + } + } + r.failed.sweep(now) +} + +// Stats reports what the relay holds. +func (r *Relay) Stats() Stats { + r.mu.Lock() + defer r.mu.Unlock() + var s Stats + for code, rm := range r.rooms { + s.Sessions++ + if code != "" { + s.Codes++ + } + s.Participants += len(rm.conns) + s.LogBytes += rm.logBytes + s.Credits += len(rm.credits) + } + return s +} + +// debugState renders every string the relay holds about its sessions and +// connections, for a test to prove what is not among them. +func (r *Relay) debugState() string { + r.mu.Lock() + defer r.mu.Unlock() + var b strings.Builder + for code, rm := range r.rooms { + fmt.Fprintf(&b, "%s %s %s %v\n", code, rm.repo, rm.hostLogin, rm.credits) + for _, o := range rm.conns { + fmt.Fprintf(&b, "%+v\n", *o) + } + } + return b.String() +} + +// CleanName makes a guest's display name safe to show and to stamp: control +// and format characters (bidirectional overrides among them) removed, spaces +// collapsed, at most 40 characters, and "Guest" when nothing is left. +func CleanName(s string) string { + var b strings.Builder + n := 0 + space := false + for _, r := range strings.TrimSpace(s) { + if unicode.IsControl(r) || unicode.Is(unicode.Cf, r) { + continue + } + if unicode.IsSpace(r) { + space = true + continue + } + if space && b.Len() > 0 { + if n >= 39 { + break + } + b.WriteRune(' ') + n++ + } + space = false + if n >= 40 { + break + } + b.WriteRune(r) + n++ + } + if b.Len() == 0 { + return "Guest" + } + return b.String() +} diff --git a/internal/core/session/relay_test.go b/internal/core/session/relay_test.go new file mode 100644 index 0000000000..992adeb160 --- /dev/null +++ b/internal/core/session/relay_test.go @@ -0,0 +1,648 @@ +package session + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "strings" + "sync" + "testing" + "time" +) + +// fakePeer is the transport's half of one connection, as a test sees it: every +// frame the relay sent, and whether the relay closed it. +type fakePeer struct { + mu sync.Mutex + frames []Frame + closed bool + policy bool + reason string + full bool +} + +func (p *fakePeer) Send(f Frame) bool { + p.mu.Lock() + defer p.mu.Unlock() + if p.full { + return false + } + p.frames = append(p.frames, Frame{Binary: f.Binary, Data: append([]byte(nil), f.Data...)}) + return true +} + +func (p *fakePeer) Close(policy bool, reason string) { + p.mu.Lock() + defer p.mu.Unlock() + p.closed, p.policy, p.reason = true, policy, reason +} + +// texts decodes every text frame the peer received. +func (p *fakePeer) texts(t *testing.T) []map[string]any { + t.Helper() + p.mu.Lock() + defer p.mu.Unlock() + var out []map[string]any + for _, f := range p.frames { + if f.Binary { + continue + } + var m map[string]any + if err := json.Unmarshal(f.Data, &m); err != nil { + t.Fatalf("a text frame that is not JSON: %q", f.Data) + } + out = append(out, m) + } + return out +} + +// last returns the last text message of type typ, or nil. +func (p *fakePeer) last(t *testing.T, typ string) map[string]any { + t.Helper() + var got map[string]any + for _, m := range p.texts(t) { + if m["type"] == typ { + got = m + } + } + return got +} + +func (p *fakePeer) binaries() [][]byte { + p.mu.Lock() + defer p.mu.Unlock() + var out [][]byte + for _, f := range p.frames { + if f.Binary { + out = append(out, f.Data) + } + } + return out +} + +func (p *fakePeer) reset() { + p.mu.Lock() + defer p.mu.Unlock() + p.frames = nil +} + +// fakeVerifier answers for tokens it was told about: token -> login and the +// repositories that login may push to. +type fakeVerifier struct { + mu sync.Mutex + logins map[string]string + push map[string]map[string]bool + calls int +} + +func (v *fakeVerifier) Verify(_ context.Context, token, repo string) (string, bool, error) { + v.mu.Lock() + defer v.mu.Unlock() + v.calls++ + login, ok := v.logins[token] + if !ok { + return "", false, errors.New("bad credentials") + } + return login, v.push[login][strings.ToLower(repo)], nil +} + +// fakeClock is the injectable clock. +type fakeClock struct { + mu sync.Mutex + now time.Time +} + +func (c *fakeClock) Now() time.Time { + c.mu.Lock() + defer c.mu.Unlock() + return c.now +} + +func (c *fakeClock) advance(d time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c.now = c.now.Add(d) +} + +type harness struct { + r *Relay + clock *fakeClock + ver *fakeVerifier +} + +func newHarness(t *testing.T, mod func(*Limits)) *harness { + t.Helper() + l := DefaultLimits() + if mod != nil { + mod(&l) + } + clock := &fakeClock{now: time.Date(2026, 10, 14, 12, 0, 0, 0, time.UTC)} + ver := &fakeVerifier{ + logins: map[string]string{"tok-ada": "ada", "tok-bea": "bea", "tok-cy": "cy"}, + push: map[string]map[string]bool{ + "ada": {"acme/brief": true}, + "bea": {"acme/brief": true}, + "cy": {}, // signed in, but may not change the repository + }, + } + return &harness{r: New(Options{Limits: l, Verifier: ver, Now: clock.Now}), clock: clock, ver: ver} +} + +func send(t *testing.T, c *Conn, v any) { + t.Helper() + data, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + c.Receive(context.Background(), Frame{Data: data}) +} + +type msg map[string]any + +// host connects a peer, verifies it with token and opens a session for +// acme/brief, returning the connection, its peer and the join code. +func (h *harness) host(t *testing.T, token string) (*Conn, *fakePeer, string) { + t.Helper() + p := &fakePeer{} + c := h.r.Connect(p, "192.0.2.1") + send(t, c, msg{"type": "verify", "token": token, "repo": "acme/brief"}) + send(t, c, msg{"type": "host", "repo": "acme/brief"}) + hosted := p.last(t, "hosted") + if hosted == nil { + t.Fatalf("no hosted answer; got %v", p.texts(t)) + } + return c, p, hosted["code"].(string) +} + +func (h *harness) join(t *testing.T, code, name, addr string) (*Conn, *fakePeer) { + t.Helper() + p := &fakePeer{} + c := h.r.Connect(p, addr) + send(t, c, msg{"type": "join", "code": code, "name": name}) + return c, p +} + +func update(b ...byte) Frame { return Frame{Binary: true, Data: append([]byte{TagUpdate}, b...)} } + +func TestHostOpensASessionAndGetsACode(t *testing.T) { + h := newHarness(t, nil) + _, p, code := h.host(t, "tok-ada") + if len(code) != 6 { + t.Fatalf("code %q is not six characters", code) + } + for _, r := range code { + if !strings.ContainsRune(CodeAlphabet, r) { + t.Fatalf("code %q has %q, outside the alphabet", code, r) + } + } + roster := p.last(t, "roster") + if roster == nil { + t.Fatal("the host got no roster") + } + parts := roster["participants"].([]any) + if len(parts) != 1 || parts[0].(map[string]any)["login"] != "ada" || parts[0].(map[string]any)["host"] != true { + t.Fatalf("roster = %v, want ada alone, as host", roster) + } + if s := h.r.Stats(); s.Sessions != 1 || s.Codes != 1 { + t.Fatalf("stats = %+v, want one session and one code", s) + } +} + +func TestHostingNeedsAVerifiedMember(t *testing.T) { + h := newHarness(t, nil) + for _, c := range []struct { + name string + token string + want string + }{ + {"unverified", "", "not-verified"}, + {"verified without push", "tok-cy", "not-a-member"}, + } { + p := &fakePeer{} + conn := h.r.Connect(p, "192.0.2.1") + if c.token != "" { + send(t, conn, msg{"type": "verify", "token": c.token, "repo": "acme/brief"}) + } + send(t, conn, msg{"type": "host", "repo": "acme/brief"}) + if e := p.last(t, "error"); e == nil || e["code"] != c.want { + t.Errorf("%s: error = %v, want %s", c.name, e, c.want) + } + } + if s := h.r.Stats(); s.Sessions != 0 { + t.Fatalf("stats = %+v, want no session", s) + } +} + +func TestCodesAreDrawnFromTheAlphabet(t *testing.T) { + r := New(Options{Rand: bytes.NewReader(bytes.Repeat([]byte{0, 30, 247, 248, 255, 31}, 10))}) + code, err := r.newCode() + if err != nil { + t.Fatal(err) + } + // 0 -> A, 30 -> 9, 247 -> 247%31 = 30 -> 9, 248 and 255 rejected, 31 -> A. + if code != "A99AA9" { + t.Fatalf("code = %q, want A99AA9 (bytes 248 and above are rejected, not folded)", code) + } +} + +func TestGuestJoinsByCodeAndReceivesTheReplayedLog(t *testing.T) { + h := newHarness(t, nil) + host, _, code := h.host(t, "tok-ada") + host.Receive(context.Background(), update(1, 2, 3)) + host.Receive(context.Background(), update(4, 5)) + + // The code is read loosely, as a person types it. + _, gp := h.join(t, strings.ToLower(code[:3])+"-"+strings.ToLower(code[3:]), "Gus", "198.51.100.7") + joined := gp.last(t, "joined") + if joined == nil || joined["role"] != "guest" || joined["name"] != "Gus" || joined["repo"] != "acme/brief" { + t.Fatalf("joined = %v, want a guest named Gus in acme/brief", joined) + } + got := gp.binaries() + if len(got) != 2 || !bytes.Equal(got[0], []byte{TagUpdate, 1, 2, 3}) || !bytes.Equal(got[1], []byte{TagUpdate, 4, 5}) { + t.Fatalf("replayed = %v, want the two updates in order", got) + } + // The replay sits between joined and synced. + var order []string + gp.mu.Lock() + for _, f := range gp.frames { + if f.Binary { + order = append(order, "update") + continue + } + var m map[string]any + _ = json.Unmarshal(f.Data, &m) + order = append(order, m["type"].(string)) + } + gp.mu.Unlock() + if strings.Join(order, ",") != "joined,update,update,synced,roster" { + t.Fatalf("order = %v", order) + } +} + +func TestGuestUpdateIsRefusedWhileAMembersIsForwarded(t *testing.T) { + h := newHarness(t, nil) + host, hp, code := h.host(t, "tok-ada") + guest, gp := h.join(t, code, "Gus", "198.51.100.7") + hp.reset() + gp.reset() + + guest.Receive(context.Background(), update(9, 9)) + if e := gp.last(t, "error"); e == nil || e["code"] != "read-only" { + t.Fatalf("guest's update: error = %v, want read-only", e) + } + if len(hp.binaries()) != 0 { + t.Fatal("a guest's update reached the host") + } + if s := h.r.Stats(); s.LogBytes != 0 { + t.Fatalf("a guest's update was logged: %+v", s) + } + + host.Receive(context.Background(), update(7)) + if got := gp.binaries(); len(got) != 1 || !bytes.Equal(got[0], []byte{TagUpdate, 7}) { + t.Fatalf("the guest received %v, want the member's update", got) + } + if len(hp.binaries()) != 0 { + t.Fatal("the sender's own update was echoed back") + } + + // Awareness is forwarded from anyone and never logged. + guest.Receive(context.Background(), Frame{Binary: true, Data: []byte{TagAwareness, 1}}) + if got := hp.binaries(); len(got) != 1 || got[0][0] != TagAwareness { + t.Fatalf("the host received %v, want the guest's awareness", got) + } + if s := h.r.Stats(); s.LogBytes != 2 { + t.Fatalf("log bytes = %d, want 2 (the one update)", s.LogBytes) + } +} + +func TestVerifyPromotesATokenWithPushAndNotOneWithout(t *testing.T) { + h := newHarness(t, nil) + _, hp, code := h.host(t, "tok-ada") + g1, p1 := h.join(t, code, "Bea", "198.51.100.7") + g2, p2 := h.join(t, code, "Cy", "198.51.100.8") + + send(t, g1, msg{"type": "verify", "token": "tok-bea"}) + if v := p1.last(t, "verified"); v == nil || v["login"] != "bea" || v["member"] != true { + t.Fatalf("bea: verified = %v, want a member", v) + } + send(t, g2, msg{"type": "verify", "token": "tok-cy"}) + if v := p2.last(t, "verified"); v == nil || v["login"] != "cy" || v["member"] != false { + t.Fatalf("cy: verified = %v, want not a member", v) + } + send(t, g2, msg{"type": "verify", "token": "tok-nobody"}) + if e := p2.last(t, "error"); e == nil || e["code"] != "verify-failed" { + t.Fatalf("a bad token: error = %v, want verify-failed", e) + } + + roles := map[string]string{} + for _, e := range hp.last(t, "roster")["participants"].([]any) { + m := e.(map[string]any) + roles[m["name"].(string)] = m["role"].(string) + } + if roles["bea"] != "member" || roles["Cy"] != "guest" { + t.Fatalf("roster roles = %v, want bea a member and Cy a guest", roles) + } + + // The promoted member's update now goes through; cy's still does not. + hp.reset() + g1.Receive(context.Background(), update(1)) + g2.Receive(context.Background(), update(2)) + if got := hp.binaries(); len(got) != 1 || got[0][1] != 1 { + t.Fatalf("host received %v, want bea's update alone", got) + } + + // The relay holds no token: nothing it can report carries one. + if strings.Contains(h.r.debugState(), "tok-") { + t.Fatal("the relay kept a token after verifying it") + } +} + +func TestVerifyIsBoundedPerConnection(t *testing.T) { + h := newHarness(t, func(l *Limits) { l.MaxVerifies = 2 }) + _, _, code := h.host(t, "tok-ada") + g, p := h.join(t, code, "Cy", "198.51.100.8") + for i := 0; i < 3; i++ { + send(t, g, msg{"type": "verify", "token": "tok-cy"}) + } + if e := p.last(t, "error"); e == nil || e["code"] != "too-many-verifies" { + t.Fatalf("error = %v, want too-many-verifies", e) + } + if h.ver.calls != 1+2 { // the host's own, then two of cy's + t.Fatalf("GitHub was asked %d times, want 3", h.ver.calls) + } +} + +func TestSuggestionGoesOnlyToMembersStampedWithTheGuestsName(t *testing.T) { + h := newHarness(t, nil) + _, hp, code := h.host(t, "tok-ada") + m2, mp := h.join(t, code, "", "198.51.100.6") + send(t, m2, msg{"type": "verify", "token": "tok-bea"}) + g, gp := h.join(t, code, "Gus", "198.51.100.7") + _, op := h.join(t, code, "Other", "198.51.100.9") + hp.reset() + mp.reset() + op.reset() + + send(t, g, msg{"type": "suggest", "name": "ada", "body": msg{"path": "brief.md", "text": "better words"}}) + for who, p := range map[string]*fakePeer{"host": hp, "member": mp} { + s := p.last(t, "suggest") + if s == nil || s["from"] != "Gus" { + t.Fatalf("%s: suggest = %v, want one stamped from Gus", who, s) + } + if body := s["body"].(map[string]any); body["text"] != "better words" { + t.Fatalf("%s: body = %v", who, body) + } + } + if s := op.last(t, "suggest"); s != nil { + t.Fatalf("another guest received a suggestion: %v", s) + } + if a := gp.last(t, "suggested"); a == nil || a["id"] == "" { + t.Fatalf("the guest got no receipt: %v", gp.texts(t)) + } + + // A suggestion that is not an object is refused; a member does not suggest. + send(t, g, msg{"type": "suggest", "body": "text"}) + if e := gp.last(t, "error"); e == nil || e["code"] != "bad-suggestion" { + t.Fatalf("error = %v, want bad-suggestion", e) + } + send(t, m2, msg{"type": "suggest", "body": msg{}}) + if e := mp.last(t, "error"); e == nil || e["code"] != "not-a-guest" { + t.Fatalf("error = %v, want not-a-guest", e) + } +} + +func TestGuestNameIsCleaned(t *testing.T) { + h := newHarness(t, nil) + _, _, code := h.host(t, "tok-ada") + _, p := h.join(t, code, " Gus‮\n"+strings.Repeat("x", 100), "198.51.100.7") + name := p.last(t, "joined")["name"].(string) + if strings.ContainsAny(name, "‮\n") || len([]rune(name)) > 40 || !strings.HasPrefix(name, "Gus") { + t.Fatalf("name = %q, want it trimmed, without control or format characters, at most 40", name) + } + _, p2 := h.join(t, code, "​", "198.51.100.8") + if n := p2.last(t, "joined")["name"]; n != "Guest" { + t.Fatalf("an empty name became %q, want Guest", n) + } +} + +func TestWrongCodesAreLimitedPerAddress(t *testing.T) { + h := newHarness(t, func(l *Limits) { l.MaxFailedJoins = 3 }) + _, _, code := h.host(t, "tok-ada") + var last *fakePeer + for i := 0; i < 3; i++ { + _, last = h.join(t, "ZZZZZZ", "Mal", "203.0.113.5") + if e := last.last(t, "error"); e == nil || e["code"] != "bad-code" { + t.Fatalf("try %d: error = %v, want bad-code", i, e) + } + } + if !last.closed || !last.policy { + t.Fatal("the third wrong code did not close the connection") + } + // Past the limit, even the right code is refused from that address, and + // the answer tells nothing about the code. + _, p := h.join(t, code, "Mal", "203.0.113.5") + if e := p.last(t, "error"); e == nil || e["code"] != "too-many-tries" || !p.closed { + t.Fatalf("error = %v, want too-many-tries and a close", e) + } + // An IPv6 visitor is counted by its /64. + for i := 0; i < 3; i++ { + h.join(t, "ZZZZZZ", "Mal", "2001:db8:1:2::"+string(rune('a'+i))) + } + if _, p := h.join(t, code, "Mal", "2001:db8:1:2::ff"); p.last(t, "error")["code"] != "too-many-tries" { + t.Fatal("rotating addresses inside one /64 escaped the limit") + } + // Another address is not held up, and the window passes. + if _, p := h.join(t, code, "Gus", "198.51.100.7"); p.last(t, "joined") == nil { + t.Fatal("another address was held up by the limit") + } + h.clock.advance(DefaultLimits().FailedJoinWindow + time.Second) + if _, p := h.join(t, code, "Mal", "203.0.113.5"); p.last(t, "joined") == nil { + t.Fatal("the limit outlived its window") + } +} + +func TestSizeLimits(t *testing.T) { + h := newHarness(t, func(l *Limits) { + l.MaxMessageBytes = 64 + l.MaxLogBytes = 10 + l.MaxParticipants = 2 + l.MaxSessions = 1 + }) + host, hp, code := h.host(t, "tok-ada") + + host.Receive(context.Background(), Frame{Binary: true, Data: append([]byte{TagUpdate}, make([]byte, 64)...)}) + if e := hp.last(t, "error"); e == nil || e["code"] != "too-big" { + t.Fatalf("an oversized frame: error = %v, want too-big", e) + } + host.Receive(context.Background(), update(make([]byte, 8)...)) + host.Receive(context.Background(), update(1, 2)) + if e := hp.last(t, "error"); e == nil || e["code"] != "log-full" { + t.Fatalf("past the log limit: error = %v, want log-full", e) + } + if s := h.r.Stats(); s.LogBytes != 9 { + t.Fatalf("log bytes = %d, want 9", s.LogBytes) + } + + h.join(t, code, "One", "198.51.100.7") + if _, p := h.join(t, code, "Two", "198.51.100.8"); p.last(t, "error")["code"] != "full" { + t.Fatal("a third participant got into a session of two") + } + + // One session per relay: a second host is refused. + p := &fakePeer{} + c := h.r.Connect(p, "192.0.2.2") + send(t, c, msg{"type": "verify", "token": "tok-bea", "repo": "acme/brief"}) + send(t, c, msg{"type": "host", "repo": "acme/brief"}) + if e := p.last(t, "error"); e == nil || e["code"] != "too-many-sessions" { + t.Fatalf("error = %v, want too-many-sessions", e) + } +} + +func TestOnlyTheHostRotatesAndCloses(t *testing.T) { + h := newHarness(t, nil) + host, hp, code := h.host(t, "tok-ada") + g, gp := h.join(t, code, "Gus", "198.51.100.7") + send(t, g, msg{"type": "rotate"}) + send(t, g, msg{"type": "close"}) + if e := gp.last(t, "error"); e == nil || e["code"] != "not-host" { + t.Fatalf("error = %v, want not-host", e) + } + send(t, host, msg{"type": "rotate"}) + next := hp.last(t, "code") + if next == nil || next["code"] == code { + t.Fatalf("rotate answered %v, want a new code", next) + } + if _, p := h.join(t, code, "Late", "198.51.100.8"); p.last(t, "error")["code"] != "bad-code" { + t.Fatal("the old code still works after a rotation") + } + if _, p := h.join(t, next["code"].(string), "Late", "198.51.100.8"); p.last(t, "joined") == nil { + t.Fatal("the new code does not work") + } +} + +func TestHostClosePurgesTheSession(t *testing.T) { + h := newHarness(t, nil) + host, _, code := h.host(t, "tok-ada") + send(t, host, msg{"type": "register", "client": 12345}) + host.Receive(context.Background(), update(1, 2, 3)) + _, gp := h.join(t, code, "Gus", "198.51.100.7") + + send(t, host, msg{"type": "close"}) + if c := gp.last(t, "closed"); c == nil || !gp.closed { + t.Fatalf("the guest was not told and closed: %v", gp.texts(t)) + } + if s := h.r.Stats(); s != (Stats{}) { + t.Fatalf("after the host closed, the relay still holds %+v", s) + } + if _, p := h.join(t, code, "Late", "198.51.100.8"); p.last(t, "error")["code"] != "bad-code" { + t.Fatal("a closed session's code still works") + } +} + +func TestIdleSessionIsPurged(t *testing.T) { + h := newHarness(t, nil) + host, _, code := h.host(t, "tok-ada") + host.Receive(context.Background(), update(1)) + g, _ := h.join(t, code, "Gus", "198.51.100.7") + host.Leave() + h.clock.advance(time.Hour) + h.r.Sweep() + if s := h.r.Stats(); s.Sessions != 1 { + t.Fatal("a session with someone still connected was purged") + } + g.Leave() + h.clock.advance(DefaultLimits().IdleAfter - time.Second) + h.r.Sweep() + if s := h.r.Stats(); s.Sessions != 1 { + t.Fatal("purged before the idle limit") + } + h.clock.advance(2 * time.Second) + h.r.Sweep() + if s := h.r.Stats(); s != (Stats{}) { + t.Fatalf("after the idle limit, the relay still holds %+v", s) + } +} + +func TestHostRejoiningByLoginKeepsTheHostRole(t *testing.T) { + h := newHarness(t, nil) + host, _, code := h.host(t, "tok-ada") + host.Leave() + p := &fakePeer{} + c := h.r.Connect(p, "192.0.2.1") + send(t, c, msg{"type": "verify", "token": "tok-ada", "repo": "acme/brief"}) + send(t, c, msg{"type": "join", "code": code}) + if j := p.last(t, "joined"); j == nil || j["role"] != "member" || j["host"] != true { + t.Fatalf("joined = %v, want the host back as a member and host", j) + } + send(t, c, msg{"type": "rotate"}) + if p.last(t, "code") == nil { + t.Fatal("the returning host could not rotate") + } +} + +func TestRegisterCreditsAClientToAMember(t *testing.T) { + h := newHarness(t, nil) + host, hp, code := h.host(t, "tok-ada") + m, mp := h.join(t, code, "", "198.51.100.6") + send(t, m, msg{"type": "verify", "token": "tok-bea"}) + g, gp := h.join(t, code, "Gus", "198.51.100.7") + + send(t, host, msg{"type": "register", "client": 4294967295}) + clients := hp.last(t, "roster")["clients"].(map[string]any) + if clients["4294967295"] != "ada" { + t.Fatalf("clients = %v, want the id credited to ada", clients) + } + send(t, m, msg{"type": "register", "client": 4294967295}) + if e := mp.last(t, "error"); e == nil || e["code"] != "client-taken" { + t.Fatalf("error = %v, want client-taken", e) + } + send(t, g, msg{"type": "register", "client": 7}) + if e := gp.last(t, "error"); e == nil || e["code"] != "read-only" { + t.Fatalf("a guest registered a client: %v", e) + } + send(t, host, msg{"type": "register", "client": -1}) + if e := hp.last(t, "error"); e == nil || e["code"] != "bad-client" { + t.Fatalf("error = %v, want bad-client", e) + } +} + +func TestMalformedMessagesAreRefused(t *testing.T) { + h := newHarness(t, nil) + p := &fakePeer{} + c := h.r.Connect(p, "192.0.2.1") + for _, raw := range []string{`not json`, `{"type":"dance"}`, `[]`} { + c.Receive(context.Background(), Frame{Data: []byte(raw)}) + } + var codes []string + for _, m := range p.texts(t) { + codes = append(codes, m["code"].(string)) + } + if strings.Join(codes, ",") != "bad-message,unknown-message,bad-message" { + t.Fatalf("codes = %v", codes) + } + c.Receive(context.Background(), update(1)) + if e := p.last(t, "error"); e["code"] != "not-in-session" { + t.Fatalf("error = %v, want not-in-session", e) + } + send(t, c, msg{"type": "verify", "token": "tok-ada", "repo": "../etc"}) + if e := p.last(t, "error"); e["code"] != "bad-repo" { + t.Fatalf("error = %v, want bad-repo", e) + } +} + +func TestSlowPeerIsDropped(t *testing.T) { + h := newHarness(t, nil) + host, _, code := h.host(t, "tok-ada") + _, gp := h.join(t, code, "Gus", "198.51.100.7") + gp.mu.Lock() + gp.full = true + gp.mu.Unlock() + host.Receive(context.Background(), update(1)) + if !gp.closed { + t.Fatal("a peer whose queue is full was not closed") + } + if s := h.r.Stats(); s.Participants != 1 { + t.Fatalf("participants = %d, want the slow peer gone", s.Participants) + } +} From 3aa6321c89a7c9dafe9f73ee4daac922bb5e0a46 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:02:00 +0100 Subject: [PATCH 007/165] feat: serve the brief session relay over HTTP and WebSocket The front door onto the relay core: the session WebSocket, the sign-in exchange that adds the team's app secret on the relay and never echoes it, the public client id, and a health check. Origins are checked against an allowlist (abcdesign.app and loopback by default), the forwarded address is trusted only behind a declared proxy, and a token is checked against GitHub's login and push permission and then dropped. Adds github.com/coder/websocket v1.8.15, the one approved dependency. Assisted-by: Claude:claude-opus-5-5 --- go.mod | 1 + go.sum | 2 + internal/surface/session/server.go | 568 ++++++++++++++++++++++++ internal/surface/session/server_test.go | 437 ++++++++++++++++++ 4 files changed, 1008 insertions(+) create mode 100644 internal/surface/session/server.go create mode 100644 internal/surface/session/server_test.go diff --git a/go.mod b/go.mod index 90682b7dc7..06a5ac0d1b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/intentdriven/abcd go 1.26.9 require ( + github.com/coder/websocket v1.8.15 github.com/minio/selfupdate v0.6.0 github.com/sigstore/sigstore-go v1.3.0 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 6afcd9c8c8..4c7a820e4c 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= +github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= diff --git a/internal/surface/session/server.go b/internal/surface/session/server.go new file mode 100644 index 0000000000..a4fbcb5e7e --- /dev/null +++ b/internal/surface/session/server.go @@ -0,0 +1,568 @@ +// Package session is the brief session relay's front door +// (itd-2610091016262335, spc-2610141709430146 step 1): the HTTP handler +// `abcd session serve` listens with. It serves four routes and nothing else: +// +// - /session, the WebSocket every participant's page holds, handed frame by +// frame to the transport-agnostic relay in internal/core/session; +// - /auth/token, the sign-in exchange: the page posts GitHub's code and its +// PKCE verifier, and the relay adds the sign-in app's secret and asks +// GitHub for the token (the token-mediating backend of the IETF's +// browser-based-apps guidance); +// - /config, the sign-in app's public client id; +// - /healthz, for the host's health check. +// +// It is abcd's second listener, admitted by adr-2610032150581128's amendment +// of 2026-10-14: started on purpose, on loopback unless an address is chosen, +// and reached on a person's machine through Tailscale Serve only. It writes +// nothing to disk and logs nothing: no token, no secret, no brief text. +package session + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "regexp" + "strings" + "sync" + "time" + + "github.com/coder/websocket" + core "github.com/intentdriven/abcd/internal/core/session" +) + +// DefaultOrigin is the page's own origin, allowed unless the relay is told +// otherwise; loopback origins are allowed too, for a page run locally. +const DefaultOrigin = "https://abcdesign.app" + +// The GitHub endpoints the relay talks to unless a test names others. +const ( + DefaultGitHubAPI = "https://api.github.com" + DefaultGitHubWeb = "https://github.com" +) + +// The server's own bounds. +const ( + maxAuthBody = 8 << 10 + githubTimeout = 15 * time.Second + writeTimeout = 10 * time.Second + // sendQueue is how many frames one connection may have waiting; a + // connection that falls further behind is dropped. + sendQueue = 1024 + // SweepInterval is how often the relay looks for idle sessions. + SweepInterval = time.Minute + // pingEvery and pingWait keep a WebSocket honest: a connection that does + // not answer a ping in time is dropped, so a vanished device does not + // hold a participant's place. + pingEvery = 30 * time.Second + pingWait = 20 * time.Second + // httpWait bounds the reading and writing of a plain request; the + // WebSocket is bounded by its pings instead, since a deadline on the + // server would outlive the upgrade. + httpWait = 30 * time.Second +) + +// Config is what the front door needs. +type Config struct { + // Origins are allowed besides DefaultOrigin and loopback, each an exact + // scheme://host[:port]. + Origins []string + // BehindProxy trusts the last X-Forwarded-For entry as the client's + // address, which is right only behind a proxy that sets it. + BehindProxy bool + // ClientID is the team's GitHub sign-in app's public id. + ClientID string + // ClientSecret is that app's secret, read from the environment by the + // caller. It never leaves the relay but in the exchange with GitHub. + ClientSecret string + // GitHubAPI and GitHubWeb default to GitHub's own. + GitHubAPI string + GitHubWeb string + // HTTPClient talks to GitHub; nil is a client with a timeout. + HTTPClient *http.Client + Limits core.Limits + Now func() time.Time + Rand io.Reader +} + +// Server is the relay's HTTP handler. +type Server struct { + cfg Config + origins map[string]bool + relay *core.Relay + client *http.Client +} + +// New builds the handler, refusing an origin that is not an exact +// scheme://host[:port] over http or https. +func New(cfg Config) (*Server, error) { + origins := map[string]bool{DefaultOrigin: true} + for _, o := range cfg.Origins { + n, ok := normalizeOrigin(o) + if !ok { + return nil, fmt.Errorf("%q is not an origin: give scheme://host[:port], over http or https, with no path or wildcard", o) + } + origins[n] = true + } + if cfg.GitHubAPI == "" { + cfg.GitHubAPI = DefaultGitHubAPI + } + if cfg.GitHubWeb == "" { + cfg.GitHubWeb = DefaultGitHubWeb + } + client := cfg.HTTPClient + if client == nil { + client = &http.Client{Timeout: githubTimeout} + } + // A redirect would carry the request somewhere GitHub did not answer + // from; the relay follows none. + c := *client + c.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse } + if c.Timeout == 0 { + c.Timeout = githubTimeout + } + s := &Server{cfg: cfg, origins: origins, client: &c} + s.relay = core.New(core.Options{ + Limits: cfg.Limits, + Verifier: GitHub{API: cfg.GitHubAPI, Client: &c}, + Now: cfg.Now, + Rand: cfg.Rand, + }) + return s, nil +} + +// Relay is the relay the handler feeds. +func (s *Server) Relay() *core.Relay { return s.relay } + +// Sweep purges idle sessions every interval until ctx ends. +func (s *Server) Sweep(ctx context.Context, interval time.Duration) { + t := time.NewTicker(interval) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return + case <-t.C: + s.relay.Sweep() + } + } +} + +// ServeHTTP is the fixed route table. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h := w.Header() + h.Set("X-Content-Type-Options", "nosniff") + h.Set("Cache-Control", "no-store") + h.Set("Referrer-Policy", "no-referrer") + if r.URL.Path != "/session" { + rc := http.NewResponseController(w) + _ = rc.SetReadDeadline(time.Now().Add(httpWait)) + _ = rc.SetWriteDeadline(time.Now().Add(httpWait)) + } + switch r.URL.Path { + case "/session": + s.serveSession(w, r) + case "/auth/token": + s.serveToken(w, r) + case "/config": + s.serveConfig(w, r) + case "/healthz": + if r.Method != http.MethodGet && r.Method != http.MethodHead { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + h.Set("Content-Type", "text/plain; charset=utf-8") + _, _ = io.WriteString(w, "ok\n") + default: + http.NotFound(w, r) + } +} + +// normalizeOrigin reads s as an exact origin: http or https, a host, an +// optional port, and nothing else. +func normalizeOrigin(s string) (string, bool) { + u, err := url.Parse(s) + if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || + u.User != nil || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.Fragment != "" || + u.Hostname() == "" || strings.ContainsAny(u.Hostname(), "*?[]") { + return "", false + } + return u.Scheme + "://" + strings.ToLower(u.Host), true +} + +// originAllowed judges a request's Origin: one of the allowed origins, or a +// loopback page. A request with no Origin is refused: every browser sends +// one on a WebSocket and on a cross-origin POST. +func (s *Server) originAllowed(origin string) bool { + n, ok := normalizeOrigin(origin) + if !ok { + return false + } + if s.origins[n] { + return true + } + u, _ := url.Parse(n) + switch u.Hostname() { + case "localhost", "127.0.0.1", "::1": + return true + } + return false +} + +// clientAddress is the address the failed-join limit counts: the socket's +// peer, or, behind a declared proxy, the last X-Forwarded-For entry (the one +// that proxy added; anything before it the client could have written). +func clientAddress(r *http.Request, behindProxy bool) string { + if behindProxy { + if fwd := r.Header.Values("X-Forwarded-For"); len(fwd) > 0 { + parts := strings.Split(fwd[len(fwd)-1], ",") + if last := strings.TrimSpace(parts[len(parts)-1]); last != "" { + return last + } + } + } + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return host +} + +// serveSession upgrades to the session's WebSocket and runs one connection. +func (s *Server) serveSession(w http.ResponseWriter, r *http.Request) { + if !s.originAllowed(r.Header.Get("Origin")) { + http.Error(w, "origin not allowed", http.StatusForbidden) + return + } + // The origin was judged above against the relay's own allowlist, which + // the library's same-host rule would widen. + ws, err := websocket.Accept(w, r, &websocket.AcceptOptions{InsecureSkipVerify: true}) + if err != nil { + return + } + ws.SetReadLimit(int64(s.relay.Limits().MaxMessageBytes)) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + p := &peer{ws: ws, out: make(chan outFrame, sendQueue), cancel: cancel} + conn := s.relay.Connect(p, clientAddress(r, s.cfg.BehindProxy)) + done := make(chan struct{}) + go func() { p.writeLoop(ctx); close(done) }() + go p.pingLoop(ctx) + for { + typ, data, err := ws.Read(ctx) + if err != nil { + break + } + conn.Receive(ctx, core.Frame{Binary: typ == websocket.MessageBinary, Data: data}) + } + conn.Leave() + cancel() + <-done + _ = ws.CloseNow() +} + +// outFrame is one queued frame, or the close that ends the queue. +type outFrame struct { + frame core.Frame + close bool + status websocket.StatusCode + reason string +} + +// peer is the relay's handle on one WebSocket: a queue its writer drains in +// order, so a close the relay asks for follows every frame queued before it. +type peer struct { + ws *websocket.Conn + out chan outFrame + cancel context.CancelFunc + mu sync.Mutex + closed bool +} + +func (p *peer) Send(f core.Frame) bool { + p.mu.Lock() + defer p.mu.Unlock() + if p.closed { + return true + } + select { + case p.out <- outFrame{frame: f}: + return true + default: + return false + } +} + +func (p *peer) Close(policy bool, reason string) { + p.mu.Lock() + defer p.mu.Unlock() + if p.closed { + return + } + p.closed = true + status := websocket.StatusNormalClosure + if policy { + status = websocket.StatusPolicyViolation + } + select { + case p.out <- outFrame{close: true, status: status, reason: reason}: + default: + p.cancel() + } +} + +// pingLoop pings the connection until ctx ends, and ends the connection when +// a ping goes unanswered. +func (p *peer) pingLoop(ctx context.Context) { + t := time.NewTicker(pingEvery) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return + case <-t.C: + pctx, cancel := context.WithTimeout(ctx, pingWait) + err := p.ws.Ping(pctx) + cancel() + if err != nil { + p.cancel() + return + } + } + } +} + +func (p *peer) writeLoop(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case f := <-p.out: + if f.close { + _ = p.ws.Close(f.status, f.reason) + p.cancel() + return + } + typ := websocket.MessageText + if f.frame.Binary { + typ = websocket.MessageBinary + } + wctx, cancel := context.WithTimeout(ctx, writeTimeout) + err := p.ws.Write(wctx, typ, f.frame.Data) + cancel() + if err != nil { + p.cancel() + return + } + } + } +} + +// writeJSON answers with a JSON body. +func writeJSON(w http.ResponseWriter, status int, v any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(v) +} + +// cors answers a request from an allowed origin with the headers a browser +// needs to read the answer, and reports whether the origin is allowed. +func (s *Server) cors(w http.ResponseWriter, r *http.Request) bool { + w.Header().Add("Vary", "Origin") + origin := r.Header.Get("Origin") + if !s.originAllowed(origin) { + return false + } + w.Header().Set("Access-Control-Allow-Origin", origin) + return true +} + +// serveConfig names the sign-in app's public client id, and whether sign-in +// is set up at all. It never names the secret. +func (s *Server) serveConfig(w http.ResponseWriter, r *http.Request) { + s.cors(w, r) + if r.Method != http.MethodGet && r.Method != http.MethodHead { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + writeJSON(w, http.StatusOK, map[string]any{ + "client_id": s.cfg.ClientID, + "sign_in": s.cfg.ClientID != "" && s.cfg.ClientSecret != "", + "authorize_url": s.cfg.GitHubWeb + "/login/oauth/authorize", + }) +} + +// pkceVerifierRe is RFC 7636's code_verifier: 43 to 128 unreserved characters. +var pkceVerifierRe = regexp.MustCompile(`^[A-Za-z0-9._~-]{43,128}$`) + +// oauthCodeRe bounds GitHub's authorization code. +var oauthCodeRe = regexp.MustCompile(`^[A-Za-z0-9._~-]{1,256}$`) + +// githubErrorRe is the shape of an OAuth error code; anything else GitHub +// says is not passed on. +var githubErrorRe = regexp.MustCompile(`^[a-z_]{1,64}$`) + +// serveToken is the sign-in exchange. +func (s *Server) serveToken(w http.ResponseWriter, r *http.Request) { + allowed := s.cors(w, r) + if r.Method == http.MethodOptions { + if !allowed { + http.Error(w, "origin not allowed", http.StatusForbidden) + return + } + w.Header().Set("Access-Control-Allow-Methods", "POST") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + w.Header().Set("Access-Control-Max-Age", "600") + w.WriteHeader(http.StatusNoContent) + return + } + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + if !allowed { + writeJSON(w, http.StatusForbidden, map[string]string{"error": "origin-not-allowed"}) + return + } + if s.cfg.ClientID == "" || s.cfg.ClientSecret == "" { + writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "sign-in-not-configured"}) + return + } + var req struct { + Code string `json:"code"` + CodeVerifier string `json:"code_verifier"` + RedirectURI string `json:"redirect_uri"` + } + dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxAuthBody)) + if err := dec.Decode(&req); err != nil || !oauthCodeRe.MatchString(req.Code) || !pkceVerifierRe.MatchString(req.CodeVerifier) { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "bad-request"}) + return + } + if req.RedirectURI != "" { + u, err := url.Parse(req.RedirectURI) + if err != nil || !u.IsAbs() || !s.originAllowed(u.Scheme+"://"+u.Host) || u.User != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "bad-redirect"}) + return + } + } + form := url.Values{ + "client_id": {s.cfg.ClientID}, + "client_secret": {s.cfg.ClientSecret}, + "code": {req.Code}, + "code_verifier": {req.CodeVerifier}, + } + if req.RedirectURI != "" { + form.Set("redirect_uri", req.RedirectURI) + } + ctx, cancel := context.WithTimeout(r.Context(), githubTimeout) + defer cancel() + greq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.cfg.GitHubWeb+"/login/oauth/access_token", strings.NewReader(form.Encode())) + if err != nil { + writeJSON(w, http.StatusBadGateway, map[string]string{"error": "exchange-failed"}) + return + } + greq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + greq.Header.Set("Accept", "application/json") + resp, err := s.client.Do(greq) + if err != nil { + writeJSON(w, http.StatusBadGateway, map[string]string{"error": "exchange-failed"}) + return + } + defer resp.Body.Close() + var got struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + Error string `json:"error"` + } + if resp.StatusCode != http.StatusOK || json.NewDecoder(io.LimitReader(resp.Body, 64<<10)).Decode(&got) != nil { + writeJSON(w, http.StatusBadGateway, map[string]string{"error": "exchange-failed"}) + return + } + // Only named fields pass, and none that carries the secret. + for _, v := range []string{got.AccessToken, got.TokenType, got.Scope, got.Error} { + if strings.Contains(v, s.cfg.ClientSecret) { + writeJSON(w, http.StatusBadGateway, map[string]string{"error": "exchange-failed"}) + return + } + } + if got.AccessToken == "" { + code := "exchange-failed" + if githubErrorRe.MatchString(got.Error) { + code = got.Error + } + writeJSON(w, http.StatusBadRequest, map[string]string{"error": code}) + return + } + writeJSON(w, http.StatusOK, map[string]string{"access_token": got.AccessToken, "token_type": got.TokenType, "scope": got.Scope}) +} + +// GitHub is the relay's Verifier: it asks GitHub's API who a token belongs to +// and whether that login may push to the repository. +type GitHub struct { + API string + Client *http.Client +} + +// errVerify is the one error a failed verify reports; it never carries the +// token or what GitHub said. +var errVerify = errors.New("GitHub did not accept the token") + +// Verify answers the login and the repository's permissions.push. A +// repository GitHub hides from the token (404) is no push, not an error. +func (g GitHub) Verify(ctx context.Context, token, repo string) (string, bool, error) { + if !core.ValidRepo(repo) { + return "", false, errors.New("not an owner/name repository") + } + var user struct { + Login string `json:"login"` + } + status, err := g.get(ctx, token, "/user", &user) + if err != nil || status != http.StatusOK || user.Login == "" { + return "", false, errVerify + } + var rp struct { + Permissions struct { + Push bool `json:"push"` + } `json:"permissions"` + } + status, err = g.get(ctx, token, "/repos/"+repo, &rp) + if err != nil { + return "", false, errVerify + } + if status != http.StatusOK { + return user.Login, false, nil + } + return user.Login, rp.Permissions.Push, nil +} + +func (g GitHub) get(ctx context.Context, token, path string, into any) (int, error) { + ctx, cancel := context.WithTimeout(ctx, githubTimeout) + defer cancel() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, g.API+path, nil) + if err != nil { + return 0, errVerify + } + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("X-GitHub-Api-Version", "2022-11-28") + client := g.Client + if client == nil { + client = &http.Client{Timeout: githubTimeout} + } + resp, err := client.Do(req) + if err != nil { + return 0, errVerify + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + if json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(into) != nil { + return 0, errVerify + } + } + return resp.StatusCode, nil +} diff --git a/internal/surface/session/server_test.go b/internal/surface/session/server_test.go new file mode 100644 index 0000000000..d8757a2e14 --- /dev/null +++ b/internal/surface/session/server_test.go @@ -0,0 +1,437 @@ +package session + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "testing" + "time" + + "github.com/coder/websocket" + core "github.com/intentdriven/abcd/internal/core/session" +) + +// testSecret is the sign-in app secret the tests configure. It is a test +// value, never a real one. +const testSecret = "test-client-secret-0123456789abcdef" + +// fakeGitHub is GitHub's API and sign-in endpoint as the relay sees them. +type fakeGitHub struct { + mu sync.Mutex + exchanges []url.Values +} + +func (g *fakeGitHub) handler() http.Handler { + logins := map[string]string{"tok-ada": "ada", "tok-bea": "bea", "tok-cy": "cy"} + push := map[string]bool{"ada": true, "bea": true} + mux := http.NewServeMux() + mux.HandleFunc("GET /user", func(w http.ResponseWriter, r *http.Request) { + login, ok := logins[strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")] + if !ok { + http.Error(w, `{"message":"Bad credentials"}`, http.StatusUnauthorized) + return + } + _ = json.NewEncoder(w).Encode(map[string]string{"login": login}) + }) + mux.HandleFunc("GET /repos/{owner}/{name}", func(w http.ResponseWriter, r *http.Request) { + login := logins[strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")] + if r.PathValue("owner") != "acme" || r.PathValue("name") != "brief" { + http.NotFound(w, r) + return + } + _ = json.NewEncoder(w).Encode(map[string]any{"full_name": "acme/brief", "permissions": map[string]bool{"pull": true, "push": push[login]}}) + }) + mux.HandleFunc("POST /login/oauth/access_token", func(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + g.mu.Lock() + g.exchanges = append(g.exchanges, r.PostForm) + g.mu.Unlock() + w.Header().Set("Content-Type", "application/json") + if r.PostForm.Get("code") != "good-code" { + // A hostile or careless upstream echoing the secret back. + _ = json.NewEncoder(w).Encode(map[string]string{ + "error": "bad_verification_code", + "error_description": "the code is wrong; client_secret=" + r.PostForm.Get("client_secret"), + }) + return + } + _ = json.NewEncoder(w).Encode(map[string]string{"access_token": "gho_session_token", "token_type": "bearer", "scope": ""}) + }) + return mux +} + +type fixture struct { + srv *Server + http *httptest.Server + github *fakeGitHub + wsURL string +} + +func newFixture(t *testing.T, mod func(*Config)) *fixture { + t.Helper() + gh := &fakeGitHub{} + ghSrv := httptest.NewServer(gh.handler()) + t.Cleanup(ghSrv.Close) + cfg := Config{ + ClientID: "Iv1.testclientid", + ClientSecret: testSecret, + GitHubAPI: ghSrv.URL, + GitHubWeb: ghSrv.URL, + HTTPClient: ghSrv.Client(), + } + if mod != nil { + mod(&cfg) + } + srv, err := New(cfg) + if err != nil { + t.Fatal(err) + } + hs := httptest.NewServer(srv) + t.Cleanup(hs.Close) + return &fixture{srv: srv, http: hs, github: gh, wsURL: "ws" + strings.TrimPrefix(hs.URL, "http") + "/session"} +} + +// client is one browser's connection as a test drives it. +type client struct { + t *testing.T + ws *websocket.Conn +} + +func (f *fixture) dial(t *testing.T, origin string) *client { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ws, _, err := websocket.Dial(ctx, f.wsURL, &websocket.DialOptions{HTTPHeader: http.Header{"Origin": {origin}}}) + if err != nil { + t.Fatalf("dial from %q: %v", origin, err) + } + t.Cleanup(func() { _ = ws.CloseNow() }) + return &client{t: t, ws: ws} +} + +func (c *client) sendJSON(v any) { + c.t.Helper() + data, _ := json.Marshal(v) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := c.ws.Write(ctx, websocket.MessageText, data); err != nil { + c.t.Fatalf("write: %v", err) + } +} + +func (c *client) sendBinary(b []byte) { + c.t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := c.ws.Write(ctx, websocket.MessageBinary, b); err != nil { + c.t.Fatalf("write: %v", err) + } +} + +// next reads one message; text messages decode to a map, binary come back raw. +func (c *client) next() (map[string]any, []byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + typ, data, err := c.ws.Read(ctx) + if err != nil { + return nil, nil, err + } + if typ == websocket.MessageBinary { + return nil, data, nil + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + c.t.Fatalf("text frame is not JSON: %q", data) + } + return m, nil, nil +} + +// until reads until a text message of type typ arrives, returning it and the +// binary frames read on the way. +func (c *client) until(typ string) (map[string]any, [][]byte) { + c.t.Helper() + var bins [][]byte + for { + m, b, err := c.next() + if err != nil { + c.t.Fatalf("waiting for %s: %v", typ, err) + } + if b != nil { + bins = append(bins, b) + continue + } + if m["type"] == typ { + return m, bins + } + } +} + +const origin = "https://abcdesign.app" + +func TestHostAndGuestOverTheWire(t *testing.T) { + f := newFixture(t, nil) + host := f.dial(t, origin) + host.sendJSON(map[string]string{"type": "verify", "token": "tok-ada", "repo": "acme/brief"}) + if v, _ := host.until("verified"); v["member"] != true { + t.Fatalf("verified = %v", v) + } + host.sendJSON(map[string]string{"type": "host", "repo": "acme/brief"}) + hosted, _ := host.until("hosted") + code := hosted["code"].(string) + host.sendBinary([]byte{core.TagUpdate, 1, 2, 3}) + + guest := f.dial(t, "http://localhost:5173") + guest.sendJSON(map[string]string{"type": "join", "code": code, "name": "Gus"}) + if j, _ := guest.until("joined"); j["role"] != "guest" { + t.Fatalf("joined = %v", j) + } + _, replay := guest.until("synced") + if len(replay) != 1 || !bytes.Equal(replay[0], []byte{core.TagUpdate, 1, 2, 3}) { + t.Fatalf("replay = %v, want the host's update", replay) + } + + guest.sendBinary([]byte{core.TagUpdate, 9}) + if e, _ := guest.until("error"); e["code"] != "read-only" { + t.Fatalf("a guest's update: %v, want read-only", e) + } + host.sendBinary([]byte{core.TagUpdate, 4}) + for { + _, b, err := guest.next() + if err != nil { + t.Fatal(err) + } + if b != nil { + if !bytes.Equal(b, []byte{core.TagUpdate, 4}) { + t.Fatalf("the guest received %v, want the host's second update", b) + } + break + } + } + + guest.sendJSON(map[string]any{"type": "suggest", "body": map[string]string{"text": "x"}}) + if s, _ := host.until("suggest"); s["from"] != "Gus" { + t.Fatalf("suggest = %v", s) + } + + host.sendJSON(map[string]string{"type": "close"}) + guest.until("closed") + if _, _, err := guest.next(); websocket.CloseStatus(err) != websocket.StatusNormalClosure { + t.Fatalf("after close the guest read %v, want a normal closure", err) + } + deadline := time.Now().Add(2 * time.Second) + for f.srv.Relay().Stats() != (core.Stats{}) { + if time.Now().After(deadline) { + t.Fatalf("the relay still holds %+v", f.srv.Relay().Stats()) + } + time.Sleep(10 * time.Millisecond) + } +} + +func TestDisallowedOriginIsRefused(t *testing.T) { + f := newFixture(t, func(c *Config) { c.Origins = []string{"https://brief.example.org"} }) + for _, o := range []string{"https://evil.example", "", "null", "https://abcdesign.app.evil.example", "http://abcdesign.app"} { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + h := http.Header{} + if o != "" { + h.Set("Origin", o) + } + _, resp, err := websocket.Dial(ctx, f.wsURL, &websocket.DialOptions{HTTPHeader: h}) + cancel() + if err == nil || resp == nil || resp.StatusCode != http.StatusForbidden { + t.Errorf("origin %q: err=%v resp=%v, want 403", o, err, resp) + } + } + for _, o := range []string{"https://abcdesign.app", "https://brief.example.org", "http://127.0.0.1:8000", "http://[::1]:3000", "http://localhost"} { + f.dial(t, o) + } +} + +func TestOversizedFrameEndsTheConnection(t *testing.T) { + f := newFixture(t, func(c *Config) { c.Limits.MaxMessageBytes = 1024 }) + c := f.dial(t, origin) + c.sendBinary(make([]byte, 2048)) + _, _, err := c.next() + if websocket.CloseStatus(err) != websocket.StatusMessageTooBig { + t.Fatalf("read after an oversized frame: %v, want close 1009", err) + } +} + +func post(t *testing.T, url, origin, body string) (*http.Response, string) { + t.Helper() + req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + if origin != "" { + req.Header.Set("Origin", origin) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + data, _ := io.ReadAll(resp.Body) + return resp, string(data) +} + +const verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + +func TestAuthTokenExchangesWithoutEverEchoingTheSecret(t *testing.T) { + f := newFixture(t, nil) + url := f.http.URL + "/auth/token" + + resp, body := post(t, url, origin, `{"code":"good-code","code_verifier":"`+verifier+`","redirect_uri":"https://abcdesign.app/acme/brief/brief/"}`) + if resp.StatusCode != http.StatusOK || !strings.Contains(body, `"access_token":"gho_session_token"`) { + t.Fatalf("exchange: %d %s", resp.StatusCode, body) + } + if resp.Header.Get("Access-Control-Allow-Origin") != origin { + t.Fatalf("CORS origin = %q", resp.Header.Get("Access-Control-Allow-Origin")) + } + f.github.mu.Lock() + got := f.github.exchanges[0] + f.github.mu.Unlock() + if got.Get("client_secret") != testSecret || got.Get("client_id") != "Iv1.testclientid" || got.Get("code_verifier") != verifier { + t.Fatalf("GitHub received %v, want the secret added and the verifier passed through", got) + } + + // GitHub's error description echoing the secret never reaches the browser. + resp, body = post(t, url, origin, `{"code":"bad-code","code_verifier":"`+verifier+`"}`) + if resp.StatusCode != http.StatusBadRequest || strings.Contains(body, testSecret) || strings.Contains(body, "client_secret") { + t.Fatalf("failed exchange: %d %s", resp.StatusCode, body) + } + if !strings.Contains(body, "bad_verification_code") { + t.Fatalf("failed exchange body %s, want GitHub's error code", body) + } + + for _, c := range []struct{ origin, body string }{ + {"https://evil.example", `{"code":"good-code","code_verifier":"` + verifier + `"}`}, + {"", `{"code":"good-code","code_verifier":"` + verifier + `"}`}, + {origin, `{"code":"good-code","code_verifier":"short"}`}, + {origin, `{"code":"good-code","code_verifier":"` + verifier + `","redirect_uri":"https://evil.example/cb"}`}, + {origin, `not json`}, + } { + resp, body := post(t, url, c.origin, c.body) + if resp.StatusCode < 400 || strings.Contains(body, testSecret) || strings.Contains(body, "gho_") { + t.Errorf("origin %q body %s: %d %s, want a refusal", c.origin, c.body, resp.StatusCode, body) + } + } + + // The configuration names the public client id and never the secret. + cfgResp, err := http.Get(f.http.URL + "/config") + if err != nil { + t.Fatal(err) + } + cfgBody, _ := io.ReadAll(cfgResp.Body) + cfgResp.Body.Close() + if !strings.Contains(string(cfgBody), "Iv1.testclientid") || strings.Contains(string(cfgBody), testSecret) { + t.Fatalf("/config = %s", cfgBody) + } +} + +func TestAuthTokenWithoutASecretIsNotConfigured(t *testing.T) { + f := newFixture(t, func(c *Config) { c.ClientSecret = "" }) + resp, body := post(t, f.http.URL+"/auth/token", origin, `{"code":"good-code","code_verifier":"`+verifier+`"}`) + if resp.StatusCode != http.StatusServiceUnavailable || !strings.Contains(body, "sign-in-not-configured") { + t.Fatalf("%d %s", resp.StatusCode, body) + } + if n := len(f.github.exchanges); n != 0 { + t.Fatalf("GitHub was asked %d times", n) + } +} + +func TestAuthTokenPreflight(t *testing.T) { + f := newFixture(t, nil) + req, _ := http.NewRequest(http.MethodOptions, f.http.URL+"/auth/token", nil) + req.Header.Set("Origin", origin) + req.Header.Set("Access-Control-Request-Method", "POST") + req.Header.Set("Access-Control-Request-Headers", "content-type") + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusNoContent || resp.Header.Get("Access-Control-Allow-Origin") != origin || + !strings.Contains(resp.Header.Get("Access-Control-Allow-Methods"), "POST") { + t.Fatalf("preflight: %d %v", resp.StatusCode, resp.Header) + } +} + +func TestHealthz(t *testing.T) { + f := newFixture(t, nil) + resp, err := http.Get(f.http.URL + "/healthz") + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("healthz = %d", resp.StatusCode) + } + resp, err = http.Get(f.http.URL + "/anything-else") + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusNotFound { + t.Fatalf("an unknown path = %d, want 404", resp.StatusCode) + } +} + +func TestGitHubVerifier(t *testing.T) { + gh := httptest.NewServer((&fakeGitHub{}).handler()) + defer gh.Close() + v := GitHub{API: gh.URL, Client: gh.Client()} + ctx := context.Background() + for _, c := range []struct { + token, repo, login string + push, err bool + }{ + {"tok-ada", "acme/brief", "ada", true, false}, + {"tok-cy", "acme/brief", "cy", false, false}, + {"tok-ada", "acme/other", "ada", false, false}, // GitHub's 404: no access + {"tok-nobody", "acme/brief", "", false, true}, + } { + login, push, err := v.Verify(ctx, c.token, c.repo) + if login != c.login || push != c.push || (err != nil) != c.err { + t.Errorf("%s on %s: login=%q push=%v err=%v", c.token, c.repo, login, push, err) + } + if err != nil && strings.Contains(err.Error(), c.token) { + t.Errorf("the error carries the token: %v", err) + } + } + if _, _, err := v.Verify(ctx, "tok-ada", "../../user"); err == nil { + t.Error("a repository that is not owner/name reached a request") + } +} + +func TestClientAddressTrustsForwardedOnlyBehindAProxy(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/session", nil) + r.RemoteAddr = "10.0.0.1:5555" + r.Header.Set("X-Forwarded-For", "203.0.113.9, 198.51.100.4") + if got := clientAddress(r, false); got != "10.0.0.1" { + t.Errorf("not behind a proxy: %q, want the socket's address", got) + } + if got := clientAddress(r, true); got != "198.51.100.4" { + t.Errorf("behind a proxy: %q, want the entry the proxy added", got) + } + r.Header.Del("X-Forwarded-For") + if got := clientAddress(r, true); got != "10.0.0.1" { + t.Errorf("behind a proxy with no header: %q, want the socket's address", got) + } +} + +func TestOriginsAreParsedStrictly(t *testing.T) { + for _, o := range []string{"https://ok.example", "http://ok.example:8080"} { + if _, err := New(Config{Origins: []string{o}}); err != nil { + t.Errorf("%s refused: %v", o, err) + } + } + for _, o := range []string{"*", "ok.example", "https://ok.example/path", "ftp://ok.example", "https://*.example"} { + if _, err := New(Config{Origins: []string{o}}); err == nil { + t.Errorf("%s accepted", o) + } + } +} From ff9eee7b60b2b2a09b8b603afc6df49b5bfa7a8d Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:04:00 +0100 Subject: [PATCH 008/165] feat: build and gate a site served below a base path A project's GitHub Pages address is owner.github.io//, and every page the site build wrote linked its stylesheet, scripts, pictures, navigation and record routes root-absolute, so served there it would load none of them. `abcd site build` and `abcd lint site` take --base-path (default /, normalised to a leading and trailing slash, plain path segments only), and every root-absolute URL goes through one helper, sitePath. The gate resolves the stylesheet against the prefixed root and a new base-path check refuses a root-absolute link outside it. The default output is byte for byte what it was: the golden files are unchanged and a build of this repository's own site before and after matches on all 3082 files. Assisted-by: Claude:claude-opus-5-5 --- .../development/brief/04-surfaces/16-lint.md | 1 + .../development/brief/04-surfaces/22-site.md | 1 + .abcd/development/release/surface.json | 14 ++ commands/lint.md | 5 + commands/site.md | 8 + docs/reference/cli/commands.md | 14 +- internal/core/site/assets.go | 8 +- internal/core/site/assets_test.go | 14 +- internal/core/site/basepath.go | 50 +++++ internal/core/site/basepath_test.go | 197 ++++++++++++++++++ internal/core/site/build.go | 12 +- internal/core/site/check.go | 56 ++++- internal/core/site/check_test.go | 4 +- internal/core/site/compose.go | 32 +-- internal/core/site/development.go | 6 +- internal/core/site/explorer.go | 22 +- internal/core/site/glossary.go | 12 +- internal/core/site/glossary_test.go | 2 +- internal/core/site/graphpage.go | 6 +- internal/core/site/health.go | 6 +- internal/core/site/markdown_test.go | 8 +- internal/core/site/recordpage.go | 2 +- internal/core/site/status.go | 2 +- internal/core/site/timeline.go | 10 +- internal/surface/cli/site.go | 14 +- internal/surface/cli/site_basepath_test.go | 31 +++ 26 files changed, 458 insertions(+), 79 deletions(-) create mode 100644 internal/core/site/basepath.go create mode 100644 internal/core/site/basepath_test.go create mode 100644 internal/surface/cli/site_basepath_test.go diff --git a/.abcd/development/brief/04-surfaces/16-lint.md b/.abcd/development/brief/04-surfaces/16-lint.md index e5869cf6fc..156ff8dab4 100644 --- a/.abcd/development/brief/04-surfaces/16-lint.md +++ b/.abcd/development/brief/04-surfaces/16-lint.md @@ -221,6 +221,7 @@ Sub-verbs: none. | Flag | Type | |---|---| +| `--base-path` | string | | `--out` | string | diff --git a/.abcd/development/brief/04-surfaces/22-site.md b/.abcd/development/brief/04-surfaces/22-site.md index 5254292def..3803f938b5 100644 --- a/.abcd/development/brief/04-surfaces/22-site.md +++ b/.abcd/development/brief/04-surfaces/22-site.md @@ -310,6 +310,7 @@ Sub-verbs: none. | Flag | Type | |---|---| +| `--base-path` | string | | `--commit` | string | | `--date` | string | | `--out` | string | diff --git a/.abcd/development/release/surface.json b/.abcd/development/release/surface.json index 2450e4a9a2..f877768e88 100644 --- a/.abcd/development/release/surface.json +++ b/.abcd/development/release/surface.json @@ -3058,6 +3058,13 @@ "hidden": false, "sentence": "Gate the built website, rendering it first when absent: Writes only inside the output directory; refuses a site failing any gate with exit 1.", "flags": [ + { + "name": "base-path", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + }, { "name": "out", "shorthand": "", @@ -3327,6 +3334,13 @@ "hidden": false, "sentence": "Render the website into the output directory: Writes there, plus a missing label into site-src/ui.json; refuses a non-empty directory it did not write.", "flags": [ + { + "name": "base-path", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + }, { "name": "commit", "shorthand": "", diff --git a/commands/lint.md b/commands/lint.md index 0d8eafabfc..31d681498b 100644 --- a/commands/lint.md +++ b/commands/lint.md @@ -189,6 +189,11 @@ one who has. It writes nothing except that render, and reaches no network. needs a browser and runs in CI. - `figure-labels` — every text label in a lifted diagram is a phrase on the page it illustrates, where the manifest asks for it. +- `base-path` — every root-absolute link a page carries sits under the path + the site is served below, given with `--base-path` (`/widgets/` for a site + served at `owner.github.io/widgets/`); the stylesheet the `mobile` gate reads + is resolved against the same path. At the default, `/`, every root-absolute + link is inside the site and the gate has nothing to find. Report the gates that passed, then every finding under the gate that raised it, each with the file and the `data-src` span a reader's next edit goes to. Exit is diff --git a/commands/site.md b/commands/site.md index 79cb6e6ae1..b393dd67f1 100644 --- a/commands/site.md +++ b/commands/site.md @@ -77,6 +77,14 @@ and the baseline is per-repo site configuration on the same opt-in footing as `.abcd/site.json` itself — the record data proper stays record-format, git and `CHANGELOG.md`. +A site served below the root of its host takes `--base-path`: a project's +GitHub Pages address is `owner.github.io//`, so its build passes +`--base-path //` and every root-absolute link the build writes (the +stylesheet, the scripts, the pictures, the navigation and every record route) +is placed under that path. Give `abcd lint site` the same `--base-path`, so it +resolves what it reads against the same root. The default, `/`, writes exactly +the links a site served at the root of its domain needs. + Four flags exist so a build can pin what the footer says rather than reading it from the working tree: `--version`, `--commit`, `--date` and `--preview` (stamp the build as unreleased at this commit, for a preview deployment of an untagged diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md index 567ee17442..e75fe38cd4 100644 --- a/docs/reference/cli/commands.md +++ b/docs/reference/cli/commands.md @@ -3295,7 +3295,8 @@ Gate the built website, rendering it first when absent: Writes only inside the o **Flags:** ``` - --out string built output directory to check (rendered first if absent) (default "site") + --base-path string path the site is served below on its host, such as /widgets/ for a project's GitHub Pages address; every root-absolute link is placed under it (default "/") + --out string built output directory to check (rendered first if absent) (default "site") ``` ### `abcd mode` @@ -3733,11 +3734,12 @@ Render the website into the output directory: Writes there, plus a missing label **Flags:** ``` - --commit string commit for the footer and the build stamp (default: git HEAD) - --date string date for the build stamp (default: the newest release's date) - --out string directory to render into (default "site") - --preview stamp the build as unreleased at this commit, for a preview deployment of an untagged tree - --version string version for the footer and the build stamp (default: the newest dated CHANGELOG heading) + --base-path string path the site is served below on its host, such as /widgets/ for a project's GitHub Pages address; every root-absolute link is placed under it (default "/") + --commit string commit for the footer and the build stamp (default: git HEAD) + --date string date for the build stamp (default: the newest release's date) + --out string directory to render into (default "site") + --preview stamp the build as unreleased at this commit, for a preview deployment of an untagged tree + --version string version for the footer and the build stamp (default: the newest dated CHANGELOG heading) ``` #### `abcd site setup` diff --git a/internal/core/site/assets.go b/internal/core/site/assets.go index 3b6bae336e..148680e5db 100644 --- a/internal/core/site/assets.go +++ b/internal/core/site/assets.go @@ -376,10 +376,12 @@ type assetPipe struct { root *os.Root // copies maps a repo-relative source to its output-relative destination. copies map[string]string + // base is the build's base path, which every picture's src sits under. + base string } -func newAssetPipe(repoRoot string, root *os.Root) *assetPipe { - return &assetPipe{repoRoot: repoRoot, root: root, copies: map[string]string{}} +func newAssetPipe(repoRoot string, root *os.Root, base string) *assetPipe { + return &assetPipe{repoRoot: repoRoot, root: root, copies: map[string]string{}, base: base} } // Copies lists the rasters to copy, sorted, as (source, destination) pairs. @@ -460,7 +462,7 @@ func (a *assetPipe) render(pageDir, src, alt string, at Source) (string, error) // `/record/adr/adr-1/`. An output-relative src resolves only at the root and // 404s everywhere else — a picture missing on hundreds of pages and present // on the one page anybody previews. - return `` + escapeAttr(alt) + ``, nil + return `` + escapeAttr(alt) + ``, nil } // pngSize reads a PNG's pixel dimensions out of its IHDR chunk, so the page can diff --git a/internal/core/site/assets_test.go b/internal/core/site/assets_test.go index ae484b2280..7e991463d9 100644 --- a/internal/core/site/assets_test.go +++ b/internal/core/site/assets_test.go @@ -41,7 +41,7 @@ func TestAssetsRefuseAnythingOutsideTheAssetRoot(t *testing.T) { "docs/assets/img/ok.svg": ``, "docs/explanation/notes.md": "prose\n", }) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") at := Source{Path: "docs/explanation/page.md", Line: 3} for _, src := range []string{ @@ -122,7 +122,7 @@ func TestAssetsRefuseExecutableSVG(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { root := assetFixture(t, map[string]string{"docs/assets/img/x.svg": c.svg}) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") out, err := pipe.render("docs/explanation", "../assets/img/x.svg", "", at) if err == nil { t.Fatalf("inlined an executable SVG into the page:\n%s", out) @@ -179,7 +179,7 @@ func TestAssetsRefuseStyledSVG(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { root := assetFixture(t, map[string]string{"docs/assets/img/x.svg": c.svg}) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") out, err := pipe.render("docs/explanation", "../assets/img/x.svg", "", at) if err == nil { t.Fatalf("inlined a styled SVG into the page:\n%s", out) @@ -208,7 +208,7 @@ func TestAssetsAcceptDrawings(t *testing.T) { hithere ` root := assetFixture(t, map[string]string{"docs/assets/img/x.svg": svg}) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") out, err := pipe.render("docs/explanation", "../assets/img/x.svg", "", Source{Path: "p.md", Line: 1}) if err != nil { t.Fatalf("a drawing was refused: %v", err) @@ -243,7 +243,7 @@ func TestAssetsStripOnlyTheRootDrawingSize(t *testing.T) { ` root := assetFixture(t, map[string]string{"docs/assets/img/x.svg": svg}) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") out, err := pipe.render("docs/explanation", "../assets/img/x.svg", "", Source{Path: "p.md", Line: 1}) if err != nil { t.Fatalf("a drawing was refused: %v", err) @@ -284,7 +284,7 @@ func TestAssetsStripOnlyTheRootDrawingSize(t *testing.T) { func TestAssetsStripTouchesTheRootTagAlone(t *testing.T) { svg := `keep width="1" me` root := assetFixture(t, map[string]string{"docs/assets/img/x.svg": svg}) - pipe := newAssetPipe(root, mustOpenRoot(t, root)) + pipe := newAssetPipe(root, mustOpenRoot(t, root), "/") out, err := pipe.render("docs/explanation", "../assets/img/x.svg", "", Source{Path: "p.md", Line: 1}) if err != nil { t.Fatalf("a drawing was refused: %v", err) @@ -313,7 +313,7 @@ func TestAssetsStripTouchesTheRootTagAlone(t *testing.T) { func TestCommittedSVGAssetsAreDrawings(t *testing.T) { repoRoot := filepath.Join("..", "..", "..") dir := filepath.Join(repoRoot, "docs", "assets", "img") - pipe := newAssetPipe(repoRoot, mustOpenRoot(t, repoRoot)) + pipe := newAssetPipe(repoRoot, mustOpenRoot(t, repoRoot), "/") at := Source{Path: "docs/explanation/page.md", Line: 1} var found int err := filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { diff --git a/internal/core/site/basepath.go b/internal/core/site/basepath.go new file mode 100644 index 0000000000..9bdf052faf --- /dev/null +++ b/internal/core/site/basepath.go @@ -0,0 +1,50 @@ +package site + +// The base path (itd-2610091010348892): the path a site is served below on its +// host. abcd's own site is served at the root of its domain, so its base path +// is "/" and every page links the shared stylesheet as /site.css. A project's +// GitHub Pages address is owner.github.io//, so a site built for it links +// //site.css instead, and every other root-absolute URL the build writes +// moves under the same prefix — the shape Hugo's baseURL, Astro's and +// VitePress's base and Docusaurus's baseUrl give the same problem. +// +// Every root-absolute URL the build writes goes through sitePath, and the gate +// resolves what it reads against the same prefix, so a build and its check +// agree on where the site's root is. + +import ( + "fmt" + "regexp" + "strings" +) + +// segmentRe is one plain path segment: the URL's unreserved characters, so a +// base path is never escaped, never interpreted and never leaves an attribute. +var segmentRe = regexp.MustCompile(`^[A-Za-z0-9._~-]+$`) + +// NormaliseBasePath is the base path s names, with one leading and one +// trailing slash: "" and "/" are the root, "widgets" is "/widgets/". It +// refuses anything but plain path segments — no scheme or host, no empty, "." +// or ".." segment, and no character a URL would have to escape. +func NormaliseBasePath(s string) (string, error) { + if s == "" || s == "/" { + return "/", nil + } + trimmed := strings.TrimSuffix(strings.TrimPrefix(s, "/"), "/") + for _, seg := range strings.Split(trimmed, "/") { + if seg == "." || seg == ".." || !segmentRe.MatchString(seg) { + return "", fmt.Errorf("site: the base path %q is not plain path segments (letters, digits and . _ ~ -, separated by single slashes)", s) + } + } + return "/" + trimmed + "/", nil +} + +// sitePath places the root-absolute site path p ("/record/") under base, a +// path NormaliseBasePath returned. At the root it returns p unchanged, so a +// build that names no base path writes the bytes it always wrote. +func sitePath(base, p string) string { + if base == "" || base == "/" { + return p + } + return base + strings.TrimPrefix(p, "/") +} diff --git a/internal/core/site/basepath_test.go b/internal/core/site/basepath_test.go new file mode 100644 index 0000000000..3923bf8786 --- /dev/null +++ b/internal/core/site/basepath_test.go @@ -0,0 +1,197 @@ +package site + +// The base path (itd-2610091010348892): a site served below the root of its +// host — a project's GitHub Pages address, owner.github.io// — links +// every page, stylesheet, script, picture and record route under that path, +// and the gate resolves what it reads against the same prefixed root. + +import ( + "bytes" + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +func TestNormaliseBasePath(t *testing.T) { + for in, want := range map[string]string{ + "": "/", + "/": "/", + "widgets": "/widgets/", + "/widgets": "/widgets/", + "widgets/": "/widgets/", + "/widgets/": "/widgets/", + "/a/b.c_d~e-f/": "/a/b.c_d~e-f/", + } { + got, err := NormaliseBasePath(in) + if err != nil || got != want { + t.Errorf("NormaliseBasePath(%q) = %q, %v; want %q", in, got, err, want) + } + } + for _, in := range []string{ + "..", "/../x/", "/a/../b/", "/./", "//", "/a//b/", "https://example.com/", "//example.com/", + "/a b/", "/a?b/", "/a#b/", "/%2e%2e/", `/a"b/`, "/a/ that +// links /record/ sends the reader to owner.github.io/record/, which is not +// this site at all. At the root base every root-absolute URL is inside it. +func (c *checker) checkBasePath() { + if c.base == "/" { + return + } + for _, page := range c.res.Pages { + for _, n := range findElements(c.pages[page], func(*htmlNode) bool { return true }) { + for _, attr := range basePathAttrs { + u := n.Attr(attr) + if !strings.HasPrefix(u, "/") || strings.HasPrefix(u, "//") || strings.HasPrefix(u, c.base) { + continue + } + c.fail(CheckBasePath, page, n.Attr("data-src"), "<%s %s=%s> links outside the base path %s the site is served below", + n.Name, attr, quote(u), c.base) + } + } + } +} diff --git a/internal/core/site/check_test.go b/internal/core/site/check_test.go index 7155f0b524..645666a879 100644 --- a/internal/core/site/check_test.go +++ b/internal/core/site/check_test.go @@ -167,8 +167,8 @@ func TestCheckPassesAWellFormedRender(t *testing.T) { t.Errorf("pages = %d, composed = %d; the record rendering should be read for the mobile gate", len(res.Pages), len(res.Composed)) } - if len(res.Checks) != 7 { - t.Errorf("checks run = %v, want seven", res.Checks) + if len(res.Checks) != 8 { + t.Errorf("checks run = %v, want eight", res.Checks) } } diff --git a/internal/core/site/compose.go b/internal/core/site/compose.go index c8a5a288b6..341be377a3 100644 --- a/internal/core/site/compose.go +++ b/internal/core/site/compose.go @@ -82,8 +82,14 @@ type composer struct { // version and releaseDate describe the newest dated CHANGELOG heading. version string releaseDate string + // base is the build's base path (basepath.go). + base string } +// at places the root-absolute site path p under the build's base path. Every +// root-absolute URL a page carries is written through it. +func (c *composer) at(p string) string { return sitePath(c.base, p) } + // docPage is one loaded source page. type docPage struct { Rel string @@ -124,7 +130,9 @@ func (c *composer) renderer(p *docPage) *Renderer { Image: func(src, alt string, at Source) (string, error) { return c.assets.render(p.Dir, src, alt, at) }, - Link: func(href string, at Source) string { return siteHref(p.Dir, href, c.repo.Repository, c.rendersDocs()) }, + Link: func(href string, at Source) string { + return siteHref(p.Dir, href, c.repo.Repository, c.rendersDocs(), c.base) + }, } } @@ -158,7 +166,7 @@ func (c *composer) rendersDocs() bool { return c.manifest.Docs.Index != "" } // moment somebody follows it. When the record explorer ships those targets get // real pages and this arm narrows; a broken link in the meantime is not an // acceptable placeholder. -func siteHref(pageDir, href, forge string, docs bool) string { +func siteHref(pageDir, href, forge string, docs bool, base string) string { switch { case href == "", strings.HasPrefix(href, "http://"), @@ -190,7 +198,7 @@ func siteHref(pageDir, href, forge string, docs bool) string { route = "" } } - out := "/docs/" + out := sitePath(base, "/docs/") if route != "" { out += route + "/" } @@ -279,10 +287,10 @@ func (c *composer) headWith(pageTitle, script string) string { b.WriteString(`` + "\n") // Root-absolute, because the shared assets sit at one served path and the // pages that link them sit at every depth the site has. - b.WriteString(`` + "\n") - b.WriteString(`` + "\n") + b.WriteString(`` + "\n") + b.WriteString(`` + "\n") if script != "" { - b.WriteString(`` + "\n") + b.WriteString(`` + "\n") } b.WriteString("\n\n") return b.String() @@ -329,7 +337,7 @@ func (c *composer) header() string { return c.headerFor("") } func (c *composer) headerFor(active string) string { var b strings.Builder b.WriteString(`
`) - b.WriteString(``) + b.WriteString(``) // The mark is a committed asset like any other picture; the stylesheet sizes // it, so its own dimensions ride along unchanged. A repository without one // gets a wordmark. @@ -350,13 +358,13 @@ func (c *composer) headerFor(active string) string { } return ` class="on" aria-current="page"` } - b.WriteString(`` + escapeText(c.ui.NavStory) + ``) - b.WriteString(`` + escapeText(c.ui.NavInstall) + ``) + b.WriteString(`` + escapeText(c.ui.NavStory) + ``) + b.WriteString(`` + escapeText(c.ui.NavInstall) + ``) if c.rendersDocs() { - b.WriteString(`` + escapeText(c.ui.NavDocs) + ``) + b.WriteString(`` + escapeText(c.ui.NavDocs) + ``) } if c.manifest.Pages.resolve().explorer { - b.WriteString(`` + escapeText(c.ui.NavRecord) + ``) + b.WriteString(`` + escapeText(c.ui.NavRecord) + ``) } if c.repo.Repository != "" { b.WriteString(`` + escapeText(c.forgeLabel()) + ` ↗`) @@ -1097,7 +1105,7 @@ func (c *composer) install(p *docPage, ch Chapter) (string, error) { // a dead link on the one page that is asking for trust. read := "" if t.isOS && hasCode && servesInstallScript { - read = `

` + + read = `

` + escapeText(c.ui.ReadScript) + `

` } hidden := " hidden" diff --git a/internal/core/site/development.go b/internal/core/site/development.go index be962bcbb4..818588c07b 100644 --- a/internal/core/site/development.go +++ b/internal/core/site/development.go @@ -162,7 +162,7 @@ func (e *explorer) developmentStorePanel(nodes []ExportNode) string { // being the same length. What was cut is said at the foot of the list // instead, where a reader is already looking at the rows it cut, in the // same shape the health page states its own remainder. - deck := developmentDeck(shown) + deck := developmentDeck(shown, e.c.base) if rest := len(group) - len(shown); rest > 0 { deck += `

` + strconv.Itoa(rest) + ` ` + escapeText(e.c.ui.More) + `

` @@ -205,11 +205,11 @@ func sortDevelopmentNodes(nodes []ExportNode) { // browses; a store here holds hundreds, and a card grid of them is a wall to // scan rather than a list to read down. The id leads and the title follows, so // the column of ids reads as an index. -func developmentDeck(nodes []ExportNode) string { +func developmentDeck(nodes []ExportNode, base string) string { var b strings.Builder b.WriteString(`
    `) for _, n := range nodes { - b.WriteString(`
  • ` + + b.WriteString(`
  • ` + escapeText(n.ID) + ``) if n.Date != "" { b.WriteString(`` + escapeText(n.Date) + ``) diff --git a/internal/core/site/explorer.go b/internal/core/site/explorer.go index ae343166fe..e4a011ee13 100644 --- a/internal/core/site/explorer.go +++ b/internal/core/site/explorer.go @@ -131,7 +131,7 @@ func newExplorer(c *composer, export RecordExport, bib *Bibliography, recordRoot for _, en := range entries { e.glossaryByPath[en.Path] = en } - e.terms = newTermLinker(entries) + e.terms = newTermLinker(entries, c.base) for _, n := range export.Nodes { e.byID[n.ID] = n e.byPath[n.Path] = n @@ -315,7 +315,7 @@ func (e *explorer) subnav(active string) string { if t.route == active { cls, cur = ` class="on"`, ` aria-current="page"` } - b.WriteString(`` + escapeText(t.label) + ``) + b.WriteString(`` + escapeText(t.label) + ``) } b.WriteString(`
`) return b.String() @@ -524,11 +524,11 @@ func (e *explorer) tileHref(typ string) string { switch typ { case "principle": if e.hasFoundations() { - return "/" + routeFoundations + "#principle" + return e.c.at("/" + routeFoundations + "#principle") } case "adr", "intent", "spec", "issue": if e.hasDevelopment() { - return "/" + routeDevelopment + "#" + typ + return e.c.at("/" + routeDevelopment + "#" + typ) } } return "" @@ -603,7 +603,7 @@ func (e *explorer) latestDecisions() string { var b strings.Builder b.WriteString(`
    `) for _, n := range adrs { - b.WriteString(`
  • ` + escapeText(n.ID) + + b.WriteString(`
  • ` + escapeText(n.ID) + `` + escapeText(n.Date) + `` + `` + escapeText(shortTitle(n)) + `
  • `) } @@ -621,8 +621,8 @@ func (e *explorer) health() string { // Two lines per finding: the fact, then its explanation — a flowing line // wrapped mid-phrase in the narrow panel. for _, u := range h.Unresolved { - b.WriteString(`
    ! ` + escapeText(u.From) + ` → ` + + b.WriteString(`
    ! ` + escapeText(u.From) + ` → ` + escapeText(u.To) + `` + escapeText(relationWord(u.Rel)) + ` ` + escapeText(ui.Record.NotInTree) + `
    `) } @@ -710,7 +710,7 @@ func (e *explorer) foundationsPage() (string, error) { var cards strings.Builder cards.WriteString(`
    `) for _, n := range nodes { - cards.WriteString(`` + + cards.WriteString(`` + `` + escapeText(shortTitle(n)) + `` + `` + escapeText(n.ID) + ``) } @@ -903,7 +903,7 @@ func (e *explorer) href(fromPath, target string) string { rel := path.Clean(path.Join(path.Dir(fromPath), file)) if strings.HasSuffix(file, ".md") { if n, ok := e.byPath[rel]; ok { - out := "/" + RecordRoute(n) + out := e.c.at("/" + RecordRoute(n)) if frag != "" { out += "#" + frag } @@ -914,13 +914,13 @@ func (e *explorer) href(fromPath, target string) string { // which is the one place a reader who followed a term link did not mean // to end up. if en, ok := e.glossaryByPath[rel]; ok { - out := "/" + en.Route + out := e.c.at("/" + en.Route) if frag != "" { out += "#" + frag } return out } - return siteHref(path.Dir(fromPath), target, e.c.repo.Repository, e.c.rendersDocs()) + return siteHref(path.Dir(fromPath), target, e.c.repo.Repository, e.c.rendersDocs(), e.c.base) } // A relative target that is NOT markdown — a directory, a script, a // configuration file. `siteHref` leaves those exactly as the record wrote diff --git a/internal/core/site/glossary.go b/internal/core/site/glossary.go index bbc929ed2d..62269fd735 100644 --- a/internal/core/site/glossary.go +++ b/internal/core/site/glossary.go @@ -115,7 +115,7 @@ func (e *explorer) glossaryIndexPage() (string, error) { var l strings.Builder l.WriteString(`
      `) for _, en := range entries { - l.WriteString(`
    • ` + + l.WriteString(`
    • ` + escapeText(en.Term.Name) + ``) if en.Term.Status != "" { l.WriteString(`` + escapeText(en.Term.Status) + ``) @@ -204,14 +204,16 @@ type termLinker struct { // lengths are the distinct spelling lengths in bytes, longest first, so // "roadmap phase" is matched before the "phase" inside it. lengths []int + // base is the build's base path, which every term link sits under. + base string } // newTermLinker indexes the entries. A spelling two entries claim goes to the // first in the scan's own order, which is the directory's order: silently // preferring one is better than linking a word to two places, and the glossary's // own GL002 rule is what stops two terms claiming one word in the first place. -func newTermLinker(entries []glossaryEntry) *termLinker { - l := &termLinker{byName: map[string]string{}} +func newTermLinker(entries []glossaryEntry, base string) *termLinker { + l := &termLinker{byName: map[string]string{}, base: base} seen := map[int]bool{} add := func(name, route string) { name = strings.TrimSpace(name) @@ -402,8 +404,8 @@ func (l *termLinker) linkRun(b *strings.Builder, run string, linked map[string]b if n := l.matchAt(run, i, linked, skipRoute); n > 0 { match := run[i : i+n] route := l.byName[asciiLower(match)] - b.WriteString(`` + match + ``) + b.WriteString(`` + match + ``) linked[route] = true i += n prevWord = true diff --git a/internal/core/site/glossary_test.go b/internal/core/site/glossary_test.go index 40bded32af..218544ef6c 100644 --- a/internal/core/site/glossary_test.go +++ b/internal/core/site/glossary_test.go @@ -205,7 +205,7 @@ func TestOneLinkPerEntryPerPage(t *testing.T) { Term: glossary.Term{Name: "phase", Aliases: []string{"roadmap phase"}}, Route: "record/glossary/core/phase/", }, - }) + }, "/") got := l.link("

      A spec is a specification, and an implementation spec too. "+ "A roadmap phase is a phase.

      ", "") want := `

      A spec is a ` + diff --git a/internal/core/site/graphpage.go b/internal/core/site/graphpage.go index 204952e840..90fe7650dd 100644 --- a/internal/core/site/graphpage.go +++ b/internal/core/site/graphpage.go @@ -23,7 +23,7 @@ func (e *explorer) graphPage() (string, error) { g := ui.Graph var b strings.Builder - b.WriteString(`

      ` + + return escapeText(word) + ` ` + escapeText(id) + `` } @@ -275,7 +275,7 @@ func (e *explorer) listTwin() string { var b strings.Builder b.WriteString(`
        `) for _, n := range nodes { - b.WriteString(`
      • ` + escapeText(n.ID) + ``) + b.WriteString(`
      • ` + escapeText(n.ID) + ``) b.WriteString(`` + escapeText(shortTitle(n)) + ``) b.WriteString(`` + escapeText(n.Date) + ``) // Each link is a LINK. The twin is the keyboard path through the chart, diff --git a/internal/core/site/health.go b/internal/core/site/health.go index f5a0ccd244..7fbbac3798 100644 --- a/internal/core/site/health.go +++ b/internal/core/site/health.go @@ -153,8 +153,8 @@ func (e *explorer) healthUnresolved() string { var b strings.Builder b.WriteString(`
        `) for _, ed := range found { - b.WriteString(`
        ! ` + escapeText(ed.From) + ` → ` + + b.WriteString(`
        ! ` + escapeText(ed.From) + ` → ` + escapeText(ed.To) + `` + escapeText(relationWord(ed.Rel)) + ` ` + escapeText(ui.Record.NotInTree) + `
        `) } @@ -202,7 +202,7 @@ func (e *explorer) healthIsolated() string { var b strings.Builder b.WriteString(``) diff --git a/internal/core/site/markdown_test.go b/internal/core/site/markdown_test.go index f4e0b9447f..07db175b99 100644 --- a/internal/core/site/markdown_test.go +++ b/internal/core/site/markdown_test.go @@ -300,7 +300,7 @@ func TestRenderRefusesOutOfSubset(t *testing.T) { // enforceable at render time rather than at review time. func TestRenderRefusesRemoteImage(t *testing.T) { dir := t.TempDir() - pipe := newAssetPipe(dir, mustOpenRoot(t, dir)) + pipe := newAssetPipe(dir, mustOpenRoot(t, dir), "/") r := testRenderer() r.Image = func(src, alt string, at Source) (string, error) { return pipe.render("docs", src, alt, at) } _, err := r.RenderBlocks("docs/page.md", Blocks("![alt](https://example.invalid/x.png)", 1)) @@ -478,18 +478,18 @@ func TestSiteHref(t *testing.T) { forge + "/blob/HEAD/.abcd/development/decisions/adrs/0047-x.md#decision"}, } for _, c := range cases { - if got := siteHref(c.dir, c.in, forge, true); got != c.want { + if got := siteHref(c.dir, c.in, forge, true, "/"); got != c.want { t.Errorf("siteHref(%q, %q) = %q, want %q", c.dir, c.in, got, c.want) } } // With no forge URL there is nothing to point at, and the record's own text // is left exactly as written. - if got := siteHref("docs/explanation", "../../CONTRIBUTING.md", "", true); got != "../../CONTRIBUTING.md" { + if got := siteHref("docs/explanation", "../../CONTRIBUTING.md", "", true, "/"); got != "../../CONTRIBUTING.md" { t.Errorf("siteHref with no forge = %q, want the href unchanged", got) } // A site with no docs tree has no docs route to send a docs page to, so // it goes to the forge's view like any other file. - if got := siteHref("docs/explanation", "roles.md#x", forge, false); got != forge+"/blob/HEAD/docs/explanation/roles.md#x" { + if got := siteHref("docs/explanation", "roles.md#x", forge, false, "/"); got != forge+"/blob/HEAD/docs/explanation/roles.md#x" { t.Errorf("siteHref with no docs tree = %q, want the forge's view of the page", got) } } diff --git a/internal/core/site/recordpage.go b/internal/core/site/recordpage.go index 26d0ccb547..27c96d4a50 100644 --- a/internal/core/site/recordpage.go +++ b/internal/core/site/recordpage.go @@ -327,7 +327,7 @@ func (e *explorer) linkRow(id, rel string) string { return "" } var b strings.Builder - b.WriteString(`
      • ` + escapeText(id) + ``) + b.WriteString(`
      • ` + escapeText(id) + ``) b.WriteString(`` + escapeText(shortTitle(target)) + ``) if rel != "" { b.WriteString(`` + escapeText(rel) + ``) diff --git a/internal/core/site/status.go b/internal/core/site/status.go index 3886d56eac..84736bd50e 100644 --- a/internal/core/site/status.go +++ b/internal/core/site/status.go @@ -51,7 +51,7 @@ func (e *explorer) statusRows(rows []statusblock.Row) string { for _, r := range rows { id := escapeText(r.ID) if route := e.routeOf(r.ID); route != "" { - id = `` + id + `` + id = `` + id + `` } b.WriteString(`
      • ` + id + `` + escapeText(r.Title) + ``) if tag := e.statusTag(r); tag != "" { diff --git a/internal/core/site/timeline.go b/internal/core/site/timeline.go index 6abd34512f..a1f9cb0710 100644 --- a/internal/core/site/timeline.go +++ b/internal/core/site/timeline.go @@ -410,9 +410,9 @@ func (e *explorer) mark(n ExportNode, p tlPoint, r float64, colour string) strin // A mark opens the record in the graph; with the graph switched off it // opens the record's own page instead, so it never points at a page the // site does not have. - href := "/" + escapeAttr(routeGraph) + "?focus=" + escapeAttr(n.ID) + href := escapeAttr(e.c.at("/" + routeGraph + "?focus=" + n.ID)) if !e.pages.graph { - href = "/" + escapeAttr(RecordRoute(n)) + href = escapeAttr(e.c.at("/" + RecordRoute(n))) } return `` + `` + escapeText(n.ID+" · "+n.Date+" · "+n.Lifecycle) + ` ` + escapeText(shortTitle(n)) + `` + @@ -553,8 +553,8 @@ func (e *explorer) supersessionList(pos map[string]tlPoint) string { if !ok { continue } - rows.WriteString(`
      • ` + - escapeText(ed.From) + ` → ` + + rows.WriteString(`
      • ` + + escapeText(ed.From) + ` → ` + escapeText(ed.To) + `` + escapeText(shortTitle(target)) + `
      • `) n++ } @@ -566,7 +566,7 @@ func (e *explorer) supersessionList(pos map[string]tlPoint) string { } sortEdgesByTarget(stubs, func(ed ExportEdge) string { return ed.From }) for _, u := range stubs { - rows.WriteString(`
      • ` + + rows.WriteString(`
      • ` + escapeText(u.From) + ` → ` + escapeText(u.To) + `` + `` + escapeText(e.c.ui.Record.NotInTree) + `
      • `) n++ diff --git a/internal/surface/cli/site.go b/internal/surface/cli/site.go index b45e31e50a..61aafd1c3c 100644 --- a/internal/surface/cli/site.go +++ b/internal/surface/cli/site.go @@ -47,7 +47,7 @@ func newSiteCommand(asJSON *bool) *cobra.Command { siteCmd.Flags().StringVar(&statusOut, "out", site.DefaultOutDir, "output directory to report on") var buildOut string - var version, commit, stampDate string + var version, commit, stampDate, buildBase string var preview bool buildCmd := &cobra.Command{ Use: "build", @@ -63,7 +63,8 @@ func newSiteCommand(asJSON *bool) *cobra.Command { Stamp: site.BuildStamp{Version: version, Commit: commit, GeneratedAt: stampDate, Preview: preview}, // The Status page's Now / Next / Later block reads the // build's state file through the loop, as the bare board does. - Lanes: loop.StatusLanes, + Lanes: loop.StatusLanes, + BasePath: buildBase, }) if err != nil { // A failure after the build completed ui.json still says what @@ -85,6 +86,7 @@ func newSiteCommand(asJSON *bool) *cobra.Command { buildCmd.Flags().StringVar(&commit, "commit", "", "commit for the footer and the build stamp (default: git HEAD)") buildCmd.Flags().StringVar(&stampDate, "date", "", "date for the build stamp (default: the newest release's date)") buildCmd.Flags().BoolVar(&preview, "preview", false, "stamp the build as unreleased at this commit, for a preview deployment of an untagged tree") + buildCmd.Flags().StringVar(&buildBase, "base-path", "/", basePathUsage) buildCmd.MarkFlagsMutuallyExclusive("preview", "version") siteCmd.AddCommand(buildCmd) @@ -93,6 +95,9 @@ func newSiteCommand(asJSON *bool) *cobra.Command { return siteCmd } +// basePathUsage describes --base-path on the two verbs that take it. +const basePathUsage = "path the site is served below on its host, such as /widgets/ for a project's GitHub Pages address; every root-absolute link is placed under it" + // siteRootAndOut resolves what a site verb reads and where it writes. The site // is a fact about the repository, not about the directory the operator stands // in, so the root is the checkout's, as the other per-repository verbs resolve @@ -117,7 +122,7 @@ func siteRootAndOut(cmd *cobra.Command, out string) (string, string, error) { // over a built output directory, rendering it first when it holds no // index.html. It exits 1 when any gate fails, so a release job can stop on it. func newLintSiteCommand(asJSON *bool) *cobra.Command { - var checkOut string + var checkOut, checkBase string checkCmd := &cobra.Command{ Use: "site", Args: cobra.NoArgs, @@ -126,7 +131,7 @@ func newLintSiteCommand(asJSON *bool) *cobra.Command { if err != nil { return err } - res, err := site.Check(site.CheckRequest{RepoRoot: root, OutDir: out, Lanes: loop.StatusLanes}) + res, err := site.Check(site.CheckRequest{RepoRoot: root, OutDir: out, Lanes: loop.StatusLanes, BasePath: checkBase}) if err != nil { return &exitError{Code: 2, Msg: "abcd lint site: " + scrubPaths(err)} } @@ -142,6 +147,7 @@ func newLintSiteCommand(asJSON *bool) *cobra.Command { }, } checkCmd.Flags().StringVar(&checkOut, "out", site.DefaultOutDir, "built output directory to check (rendered first if absent)") + checkCmd.Flags().StringVar(&checkBase, "base-path", "/", basePathUsage) return checkCmd } diff --git a/internal/surface/cli/site_basepath_test.go b/internal/surface/cli/site_basepath_test.go new file mode 100644 index 0000000000..1c1fb8fd58 --- /dev/null +++ b/internal/surface/cli/site_basepath_test.go @@ -0,0 +1,31 @@ +package cli + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/intentdriven/abcd/internal/gittest" +) + +// TestSiteVerbsTakeABasePath: `site build` and `lint site` both take the path +// the site is served below (itd-2610091010348892), and both refuse one that is +// not plain path segments before reading anything else. +func TestSiteVerbsTakeABasePath(t *testing.T) { + hermeticEnv(t) + repo := gittest.NewRepo(t) + t.Chdir(repo.Root()) + for _, verb := range [][]string{{"site", "build"}, {"lint", "site"}} { + out := filepath.Join(t.TempDir(), "site") + args := append(append([]string{}, verb...), "--out", out, "--base-path", "/../elsewhere/") + got, err := runCLIErr(t, args...) + if code := exitCodeOf(err); code != 2 || !strings.Contains(err.Error(), "base path") { + t.Fatalf("`abcd %s --base-path /../elsewhere/` = exit %d, %v:\n%s", strings.Join(verb, " "), code, err, got) + } + args = append(append([]string{}, verb...), "--out", out, "--base-path", "/widgets/") + _, err = runCLIErr(t, args...) + if err == nil || strings.Contains(err.Error(), "base path") || strings.Contains(err.Error(), "unknown flag") { + t.Fatalf("`abcd %s --base-path /widgets/` was refused for its base path: %v", strings.Join(verb, " "), err) + } + } +} From a2ab10f7a31e51fc524ac0cd1b934cbc398f4229 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:04:15 +0100 Subject: [PATCH 009/165] feat: render the team relay's Render blueprint and Dockerfile The blueprint pins one abcd release and the SHA-256 its checksums.txt publishes for abcd-linux-amd64, read when the files are written. The Dockerfile checks the download against that digest and runs the relay on 0.0.0.0:$PORT behind the host's proxy; render.yaml declares one free docker web service with a /healthz check and the sign-in secret as an unsynced variable. Each file carries a digest of its own body, so a rerun replaces an untouched file and refuses an edited one, writing nothing. Assisted-by: Claude:claude-opus-5-5 --- internal/core/session/blueprint.go | 281 ++++++++++++++++++ internal/core/session/blueprint_test.go | 216 ++++++++++++++ .../testdata/blueprint/Dockerfile.golden | 15 + .../testdata/blueprint/render.yaml.golden | 24 ++ 4 files changed, 536 insertions(+) create mode 100644 internal/core/session/blueprint.go create mode 100644 internal/core/session/blueprint_test.go create mode 100644 internal/core/session/testdata/blueprint/Dockerfile.golden create mode 100644 internal/core/session/testdata/blueprint/render.yaml.golden diff --git a/internal/core/session/blueprint.go b/internal/core/session/blueprint.go new file mode 100644 index 0000000000..cc68c0ac16 --- /dev/null +++ b/internal/core/session/blueprint.go @@ -0,0 +1,281 @@ +package session + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "os" + "regexp" + "strings" + + "github.com/intentdriven/abcd/internal/fsutil" +) + +// The blueprint (spc-2610141709430146 step 2): `abcd session blueprint` writes +// a Render Blueprint and a Dockerfile under .abcd/relay/, so a team deploys its +// own relay once to its own account. The Dockerfile downloads the released +// abcd binary for linux/amd64 and checks it against the digest that release's +// checksums.txt published, fetched and pinned when the files are written. + +// BlueprintDir is where the blueprint is written, relative to the checkout. +const BlueprintDir = ".abcd/relay" + +// relayAsset is the release asset the relay runs: Render builds and runs on +// linux/amd64. +const relayAsset = "abcd-linux-amd64" + +// releaseOrigin is where abcd's releases are published. +const releaseOrigin = "https://github.com/intentdriven/abcd" + +// releaseTagRe is the release tag a blueprint may pin: vMAJOR.MINOR.PATCH with +// an optional pre-release. The tag travels into a URL and a Dockerfile, so +// nothing else is accepted. +var releaseTagRe = regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.]+)?$`) + +// ValidReleaseTag reports whether tag is one a blueprint may pin. +func ValidReleaseTag(tag string) bool { return releaseTagRe.MatchString(tag) } + +// AssetFetcher fetches one named asset of a release; found is false, with no +// error, when the release or the asset does not exist. +type AssetFetcher interface { + FetchReleaseAsset(tag, name string) (data []byte, url string, found bool, err error) +} + +// BlueprintRequest names the checkout and the release to pin. +type BlueprintRequest struct { + // Root is the checkout the blueprint is written into. + Root string + // Release is the abcd release tag the relay runs. + Release string + // Fetch reads the release's checksums.txt. + Fetch AssetFetcher +} + +// The states a blueprint file can be left in. +const ( + BlueprintWritten = "written" + BlueprintReplaced = "replaced" + BlueprintUnchanged = "unchanged" +) + +// BlueprintFile is one file the verb wrote or left. +type BlueprintFile struct { + Path string `json:"path"` + State string `json:"state"` +} + +// BlueprintResult is what the verb did. +type BlueprintResult struct { + Release string `json:"release"` + Asset string `json:"asset"` + Digest string `json:"sha256"` + Source string `json:"checksums_url"` + Files []BlueprintFile `json:"files"` +} + +// BlueprintRefusal is the refusal to overwrite a file someone edited, or one +// abcd did not write. Nothing is written when it is returned. +type BlueprintRefusal struct { + Edited []string +} + +func (r *BlueprintRefusal) Error() string { + return "refusing to overwrite " + strings.Join(r.Edited, " and ") + + ": edited since abcd wrote it, or not written by abcd; move it aside or delete it to write the blueprint again (nothing written)" +} + +// blueprintFiles is the order the files are written and reported in. +var blueprintFiles = []string{"Dockerfile", "render.yaml"} + +// headerRe reads a blueprint file's first line: the release it pins and the +// digest of everything after that line, as written. +var headerRe = regexp.MustCompile(`^# abcd session blueprint (\S+) sha256:([0-9a-f]{64})$`) + +// stamp prefixes body with the header line that lets a later run tell an +// untouched file from an edited one. +func stamp(release string, body []byte) []byte { + sum := sha256.Sum256(body) + return append([]byte(fmt.Sprintf("# abcd session blueprint %s sha256:%s\n", release, hex.EncodeToString(sum[:]))), body...) +} + +// untouched reports whether data is a blueprint file exactly as abcd wrote it. +func untouched(data []byte) bool { + line, body, ok := bytes.Cut(data, []byte("\n")) + if !ok { + return false + } + m := headerRe.FindSubmatch(line) + if m == nil { + return false + } + sum := sha256.Sum256(body) + return hex.EncodeToString(sum[:]) == string(m[2]) +} + +// RenderBlueprint renders the two files for release, pinned to digest, the +// SHA-256 of release's abcd-linux-amd64. It reads and writes nothing. +func RenderBlueprint(release, digest string) map[string][]byte { + docker := `# The team's own relay for abcd's live brief sessions. It downloads the +# released abcd binary for linux/amd64, checks it against the checksum that +# release published (pinned below when this file was written), and runs the +# relay behind the host's proxy. abcd refuses to overwrite this file once it is +# edited; delete it and run ` + "`abcd session blueprint`" + ` to write it again. +FROM --platform=linux/amd64 alpine:3.22 +RUN wget -q -O /usr/local/bin/abcd "` + releaseOrigin + `/releases/download/` + release + `/` + relayAsset + `" \ + && echo "` + digest + ` /usr/local/bin/abcd" | sha256sum -c - \ + && chmod 0755 /usr/local/bin/abcd \ + && adduser -D -H relay +USER relay +ENV PORT=10000 +EXPOSE 10000 +CMD ["sh", "-c", "exec abcd session serve --listen \"0.0.0.0:${PORT}\" --behind-proxy"] +` + yaml := `# The team's own brief session relay: one free web service built from the +# Dockerfile beside this file. On Render, choose New, then Blueprint, pick this +# repository and set the Blueprint path to .abcd/relay/render.yaml. One deploy +# serves all of the team's sessions. A session lives in the relay's memory +# only, so a redeploy ends every open session; that is why autoDeploy is off. +# abcd refuses to overwrite this file once it is edited. +services: + - type: web + name: abcd-session-relay + runtime: docker + plan: free + dockerfilePath: ./.abcd/relay/Dockerfile + dockerContext: ./.abcd/relay + healthCheckPath: /healthz + autoDeploy: false + envVars: + # The team's own GitHub sign-in app. Set both in Render's dashboard + # (the service's Environment), never in git. The secret stays on the + # relay; the client id is public. + - key: ABCD_SESSION_GITHUB_CLIENT_ID + sync: false + - key: ABCD_SESSION_GITHUB_CLIENT_SECRET + sync: false +` + return map[string][]byte{ + "Dockerfile": stamp(release, []byte(docker)), + "render.yaml": stamp(release, []byte(yaml)), + } +} + +// maxBlueprintRead caps the read of an existing blueprint file. +const maxBlueprintRead = 1 << 20 + +// WriteBlueprint fetches the release's checksum, renders the blueprint and +// writes it into the checkout. It refuses, writing nothing, when the release +// tag is not one, when the release publishes no checksum for the linux/amd64 +// binary, and when either file exists in a form abcd did not write. +func WriteBlueprint(req BlueprintRequest) (BlueprintResult, error) { + res := BlueprintResult{Release: req.Release, Asset: relayAsset} + if !ValidReleaseTag(req.Release) { + return res, fmt.Errorf("%q is not a release tag (vMAJOR.MINOR.PATCH)", req.Release) + } + if req.Fetch == nil { + return res, errors.New("no way to read the release's checksums") + } + raw, url, found, err := req.Fetch.FetchReleaseAsset(req.Release, "checksums.txt") + res.Source = url + if err != nil { + return res, fmt.Errorf("reading %s's checksums.txt: %w", req.Release, err) + } + if !found { + return res, fmt.Errorf("release %s publishes no checksums.txt at %s", req.Release, url) + } + sums, err := parseChecksums(raw) + if err != nil { + return res, fmt.Errorf("%s's checksums.txt: %w", req.Release, err) + } + digest, ok := sums[relayAsset] + if !ok { + return res, fmt.Errorf("%s's checksums.txt names no %s", req.Release, relayAsset) + } + res.Digest = digest + files := RenderBlueprint(req.Release, digest) + + if !fsutil.IsRealDir(req.Root) { + return res, fmt.Errorf("%s is not a directory", req.Root) + } + // Judge both files before writing either, so a refusal writes nothing. + ok, err = fsutil.ProbeRealDirAll(req.Root, BlueprintDir) + if err != nil { + return res, fmt.Errorf("%s/ is not a plain directory: %w", BlueprintDir, err) + } + states := map[string]string{} + var edited []string + if ok { + root, err := os.OpenRoot(req.Root) + if err != nil { + return res, err + } + defer root.Close() + for _, name := range blueprintFiles { + rel := BlueprintDir + "/" + name + have, err := fsutil.ReadGuardedInRoot(root, rel, maxBlueprintRead) + switch { + case errors.Is(err, os.ErrNotExist): + states[name] = BlueprintWritten + case err != nil: + return res, fmt.Errorf("reading %s: %w", rel, err) + case bytes.Equal(have, files[name]): + states[name] = BlueprintUnchanged + case untouched(have): + states[name] = BlueprintReplaced + default: + edited = append(edited, rel) + } + } + } + if len(edited) > 0 { + return res, &BlueprintRefusal{Edited: edited} + } + if err := fsutil.EnsureRealDirAll(req.Root, BlueprintDir, 0o755); err != nil { + return res, fmt.Errorf("creating %s/: %w", BlueprintDir, err) + } + root, err := os.OpenRoot(req.Root) + if err != nil { + return res, err + } + defer root.Close() + for _, name := range blueprintFiles { + rel := BlueprintDir + "/" + name + state := states[name] + if state == "" { + state = BlueprintWritten + } + if state != BlueprintUnchanged { + if err := fsutil.WriteFileAtomicInRoot(root, rel, files[name], 0o644); err != nil { + return res, fmt.Errorf("writing %s: %w", rel, err) + } + } + res.Files = append(res.Files, BlueprintFile{Path: rel, State: state}) + } + return res, nil +} + +// parseChecksums reads a sha256sum manifest ("<64 hex> " per line, a +// '*' binary marker tolerated). A malformed line refuses the whole manifest. +func parseChecksums(raw []byte) (map[string]string, error) { + sums := map[string]string{} + for _, line := range strings.Split(string(raw), "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + f := strings.Fields(line) + if len(f) != 2 || len(f[0]) != 64 { + return nil, errors.New("a malformed line") + } + if _, err := hex.DecodeString(f[0]); err != nil { + return nil, errors.New("a malformed digest") + } + sums[strings.TrimPrefix(f[1], "*")] = strings.ToLower(f[0]) + } + if len(sums) == 0 { + return nil, errors.New("no assets") + } + return sums, nil +} diff --git a/internal/core/session/blueprint_test.go b/internal/core/session/blueprint_test.go new file mode 100644 index 0000000000..b178ce8a02 --- /dev/null +++ b/internal/core/session/blueprint_test.go @@ -0,0 +1,216 @@ +package session + +import ( + "errors" + "flag" + "os" + "path/filepath" + "strings" + "testing" +) + +var updateBlueprint = flag.Bool("update-blueprint", false, "rewrite the blueprint goldens under testdata from this run's output") + +// testDigest is the checksum the fake release publishes for the linux/amd64 +// binary. +const testDigest = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + +// fakeRelease is a release's assets, served without a network. +type fakeRelease struct { + checksums string + found bool + err error + asked []string +} + +func (f *fakeRelease) FetchReleaseAsset(tag, name string) ([]byte, string, bool, error) { + f.asked = append(f.asked, tag+"/"+name) + url := "https://github.com/intentdriven/abcd/releases/download/" + tag + "/" + name + if f.err != nil { + return nil, url, false, f.err + } + if !f.found { + return nil, url, false, nil + } + return []byte(f.checksums), url, true, nil +} + +func goodRelease() *fakeRelease { + return &fakeRelease{found: true, checksums: "" + + "1111111111111111111111111111111111111111111111111111111111111111 abcd-darwin-arm64\n" + + testDigest + " abcd-linux-amd64\n" + + "2222222222222222222222222222222222222222222222222222222222222222 abcd-linux-arm64\n"} +} + +func golden(t *testing.T, name string, got []byte) { + t.Helper() + path := filepath.Join("testdata", "blueprint", name) + if *updateBlueprint { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, got, 0o644); err != nil { + t.Fatal(err) + } + } + want, err := os.ReadFile(path) + if err != nil { + t.Fatalf("%v (run with -update-blueprint to write it)", err) + } + if string(got) != string(want) { + t.Fatalf("%s differs from its golden:\n--- got\n%s\n--- want\n%s", name, got, want) + } +} + +func TestBlueprintMatchesItsGoldens(t *testing.T) { + root := t.TempDir() + rel := goodRelease() + res, err := WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.1", Fetch: rel}) + if err != nil { + t.Fatal(err) + } + if res.Digest != testDigest || res.Asset != "abcd-linux-amd64" || !strings.HasSuffix(res.Source, "/v0.14.1/checksums.txt") { + t.Fatalf("result = %+v", res) + } + if strings.Join(rel.asked, ",") != "v0.14.1/checksums.txt" { + t.Fatalf("fetched %v, want the release's checksums alone", rel.asked) + } + for _, f := range []string{"Dockerfile", "render.yaml"} { + data, err := os.ReadFile(filepath.Join(root, ".abcd", "relay", f)) + if err != nil { + t.Fatal(err) + } + golden(t, f+".golden", data) + } + for _, f := range res.Files { + if f.State != "written" { + t.Fatalf("a first write reported %+v", f) + } + } +} + +func TestBlueprintPinsTheReleaseAndItsChecksum(t *testing.T) { + files := RenderBlueprint("v0.14.1", testDigest) + docker := string(files["Dockerfile"]) + for _, want := range []string{ + "https://github.com/intentdriven/abcd/releases/download/v0.14.1/abcd-linux-amd64", + testDigest + " /usr/local/bin/abcd\" | sha256sum -c -", + "session serve --listen \\\"0.0.0.0:${PORT}\\\" --behind-proxy", + "--platform=linux/amd64", + } { + if !strings.Contains(docker, want) { + t.Errorf("Dockerfile lacks %q", want) + } + } + if strings.Contains(docker, "ARG ") { + t.Error("the pin is a build argument, which a build could override") + } + yaml := string(files["render.yaml"]) + for _, want := range []string{ + "runtime: docker", "plan: free", "dockerfilePath: ./.abcd/relay/Dockerfile", "healthCheckPath: /healthz", + "- key: ABCD_SESSION_GITHUB_CLIENT_SECRET\n sync: false", + } { + if !strings.Contains(yaml, want) { + t.Errorf("render.yaml lacks %q", want) + } + } + if strings.Count(yaml, "- type: web") != 1 { + t.Error("render.yaml declares other than one web service") + } + if strings.Contains(strings.ToLower(yaml), "funnel") || strings.Contains(strings.ToLower(docker), "funnel") { + t.Error("the blueprint mentions Funnel") + } +} + +func TestBlueprintRefusesToOverwriteAnEditedFile(t *testing.T) { + root := t.TempDir() + if _, err := WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.0", Fetch: goodRelease()}); err != nil { + t.Fatal(err) + } + // Unedited: the same release is unchanged, a new release replaces it. + res, err := WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.0", Fetch: goodRelease()}) + if err != nil || res.Files[0].State != "unchanged" || res.Files[1].State != "unchanged" { + t.Fatalf("rerun: %+v %v, want both unchanged", res, err) + } + res, err = WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.1", Fetch: goodRelease()}) + if err != nil || res.Files[0].State != "replaced" { + t.Fatalf("new release: %+v %v, want replaced", res, err) + } + + // Edited: refused, and neither file is touched. + docker := filepath.Join(root, ".abcd", "relay", "Dockerfile") + yaml := filepath.Join(root, ".abcd", "relay", "render.yaml") + edited := []byte(strings.Replace(string(mustRead(t, yaml)), "plan: free", "plan: starter", 1)) + if err := os.WriteFile(yaml, edited, 0o644); err != nil { + t.Fatal(err) + } + before := mustRead(t, docker) + _, err = WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.2", Fetch: goodRelease()}) + var ref *BlueprintRefusal + if !errors.As(err, &ref) || len(ref.Edited) != 1 || ref.Edited[0] != ".abcd/relay/render.yaml" { + t.Fatalf("err = %v, want a refusal naming render.yaml", err) + } + if string(mustRead(t, yaml)) != string(edited) || string(mustRead(t, docker)) != string(before) { + t.Fatal("a refused write changed a file") + } + + // A file abcd did not write is never overwritten either. + if err := os.WriteFile(docker, []byte("FROM scratch\n"), 0o644); err != nil { + t.Fatal(err) + } + _ = os.Remove(yaml) + if _, err := WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.2", Fetch: goodRelease()}); !errors.As(err, &ref) { + t.Fatalf("a foreign Dockerfile: err = %v, want a refusal", err) + } +} + +func TestBlueprintRefusals(t *testing.T) { + for _, c := range []struct { + name string + release string + fetch *fakeRelease + want string + }{ + {"not a tag", "latest", goodRelease(), "not a release tag"}, + {"a path in the tag", "v1.0.0/../x", goodRelease(), "not a release tag"}, + {"no release", "v9.9.9", &fakeRelease{}, "publishes no checksums.txt"}, + {"no linux binary", "v0.14.1", &fakeRelease{found: true, checksums: "1111111111111111111111111111111111111111111111111111111111111111 abcd-darwin-arm64\n"}, "names no abcd-linux-amd64"}, + {"garbage", "v0.14.1", &fakeRelease{found: true, checksums: "not a manifest\n"}, "malformed"}, + {"the network", "v0.14.1", &fakeRelease{err: errors.New("offline")}, "offline"}, + } { + root := t.TempDir() + _, err := WriteBlueprint(BlueprintRequest{Root: root, Release: c.release, Fetch: c.fetch}) + if err == nil || !strings.Contains(err.Error(), c.want) { + t.Errorf("%s: err = %v, want %q", c.name, err, c.want) + } + if _, statErr := os.Stat(filepath.Join(root, ".abcd")); statErr == nil { + t.Errorf("%s: a refusal created .abcd/", c.name) + } + } +} + +func TestBlueprintRefusesASymlinkedDirectory(t *testing.T) { + root := t.TempDir() + elsewhere := t.TempDir() + if err := os.MkdirAll(filepath.Join(root, ".abcd"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.Symlink(elsewhere, filepath.Join(root, ".abcd", "relay")); err != nil { + t.Fatal(err) + } + if _, err := WriteBlueprint(BlueprintRequest{Root: root, Release: "v0.14.1", Fetch: goodRelease()}); err == nil { + t.Fatal("wrote through a symlinked .abcd/relay") + } + if entries, _ := os.ReadDir(elsewhere); len(entries) != 0 { + t.Fatal("a file landed outside the checkout") + } +} + +func mustRead(t *testing.T, p string) []byte { + t.Helper() + b, err := os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + return b +} diff --git a/internal/core/session/testdata/blueprint/Dockerfile.golden b/internal/core/session/testdata/blueprint/Dockerfile.golden new file mode 100644 index 0000000000..7dbe40727e --- /dev/null +++ b/internal/core/session/testdata/blueprint/Dockerfile.golden @@ -0,0 +1,15 @@ +# abcd session blueprint v0.14.1 sha256:d476ec6f904ca58647dd45f12ad0efdfc4f77f52c3fa14ef5fb77fb8a89e56a1 +# The team's own relay for abcd's live brief sessions. It downloads the +# released abcd binary for linux/amd64, checks it against the checksum that +# release published (pinned below when this file was written), and runs the +# relay behind the host's proxy. abcd refuses to overwrite this file once it is +# edited; delete it and run `abcd session blueprint` to write it again. +FROM --platform=linux/amd64 alpine:3.22 +RUN wget -q -O /usr/local/bin/abcd "https://github.com/intentdriven/abcd/releases/download/v0.14.1/abcd-linux-amd64" \ + && echo "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef /usr/local/bin/abcd" | sha256sum -c - \ + && chmod 0755 /usr/local/bin/abcd \ + && adduser -D -H relay +USER relay +ENV PORT=10000 +EXPOSE 10000 +CMD ["sh", "-c", "exec abcd session serve --listen \"0.0.0.0:${PORT}\" --behind-proxy"] diff --git a/internal/core/session/testdata/blueprint/render.yaml.golden b/internal/core/session/testdata/blueprint/render.yaml.golden new file mode 100644 index 0000000000..2533f5a1f2 --- /dev/null +++ b/internal/core/session/testdata/blueprint/render.yaml.golden @@ -0,0 +1,24 @@ +# abcd session blueprint v0.14.1 sha256:d62dec5bbfc9d6e59645aaeb329193d5d7ad6937fc09236d226dc6c3e3b859ab +# The team's own brief session relay: one free web service built from the +# Dockerfile beside this file. On Render, choose New, then Blueprint, pick this +# repository and set the Blueprint path to .abcd/relay/render.yaml. One deploy +# serves all of the team's sessions. A session lives in the relay's memory +# only, so a redeploy ends every open session; that is why autoDeploy is off. +# abcd refuses to overwrite this file once it is edited. +services: + - type: web + name: abcd-session-relay + runtime: docker + plan: free + dockerfilePath: ./.abcd/relay/Dockerfile + dockerContext: ./.abcd/relay + healthCheckPath: /healthz + autoDeploy: false + envVars: + # The team's own GitHub sign-in app. Set both in Render's dashboard + # (the service's Environment), never in git. The secret stays on the + # relay; the client id is public. + - key: ABCD_SESSION_GITHUB_CLIENT_ID + sync: false + - key: ABCD_SESSION_GITHUB_CLIENT_SECRET + sync: false From ffa77caf536c2518c354ade22e83d7016959008a Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:12:23 +0100 Subject: [PATCH 010/165] feat: open every site page with the managed-repository line Every page the site build writes now opens with a line saying it is the record of an abcd-managed repository, linking to the repository. The words are a new interface string, managed_record, in the closed ui.json allowlist (abcd's own and the seed setup writes, which stay byte-equal), so an older ui.json gains it with its default words and the provenance gate still refuses any word no repository file accounts for. A managed repository with no package manifest takes its repository address from the origin remote on github.com, which a release workflow's checkout carries. Assisted-by: Claude:claude-opus-5-5 --- commands/site.md | 8 ++- internal/core/site/build.go | 18 ++++- internal/core/site/compose.go | 13 ++++ internal/core/site/fixture_test.go | 3 +- internal/core/site/managedline_test.go | 69 +++++++++++++++++++ internal/core/site/setupsrc/site.css | 4 ++ internal/core/site/setupsrc/ui.json | 1 + internal/core/site/testdata/contributors.html | 2 +- internal/core/site/testdata/index.html | 2 +- internal/core/site/testdata/record-adr-1.html | 2 +- .../core/site/testdata/record-dashboard.html | 2 +- .../site/testdata/record-foundations.html | 2 +- internal/core/site/testdata/record-graph.html | 2 +- internal/core/site/testdata/record-itd-2.html | 2 +- .../core/site/testdata/record-principle.html | 2 +- internal/core/site/testdata/references.html | 2 +- internal/core/site/ui.go | 5 ++ site-src/site.css | 4 ++ site-src/ui.json | 1 + 19 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 internal/core/site/managedline_test.go diff --git a/commands/site.md b/commands/site.md index b393dd67f1..127d78ff78 100644 --- a/commands/site.md +++ b/commands/site.md @@ -56,7 +56,8 @@ reads exactly this set — `.abcd/site.json`, `site-src/ui.json`, the root or in `docs/` — and `CITATION.cff` for the footer), `.abcd/site-baseline.json` (the ratchet the health block counts against), and `.claude-plugin/plugin.json` (the forge URL, licence and author the links and -footer use) — and writes the landing page, the record +footer use; without a forge URL there, the origin remote's github.com address) +— and writes the landing page, the record export, the redirect and header maps, the stylesheet, the two scripts, the `install.sh`, and every referenced raster into the output directory. The one write outside it is `site-src/ui.json` itself, and only when the file lacks a @@ -85,6 +86,11 @@ is placed under that path. Give `abcd lint site` the same `--base-path`, so it resolves what it reads against the same root. The default, `/`, writes exactly the links a site served at the root of its domain needs. +Every page opens with one line saying it is the record of an abcd-managed +repository, linking to that repository. Its words are the `managed_record` +interface string in `site-src/ui.json`, so like every other word the generator +adds, they are declared there and nowhere else. + Four flags exist so a build can pin what the footer says rather than reading it from the working tree: `--version`, `--commit`, `--date` and `--preview` (stamp the build as unreleased at this commit, for a preview deployment of an untagged diff --git a/internal/core/site/build.go b/internal/core/site/build.go index 5f752bf95f..e7b7bc8ffa 100644 --- a/internal/core/site/build.go +++ b/internal/core/site/build.go @@ -24,6 +24,7 @@ import ( "strings" "syscall" + "github.com/intentdriven/abcd/internal/core/ahoy" "github.com/intentdriven/abcd/internal/core/changelog" "github.com/intentdriven/abcd/internal/core/lint" "github.com/intentdriven/abcd/internal/core/statusblock" @@ -240,8 +241,23 @@ type RepoMeta struct { } // LoadRepoMeta reads the package manifest. An absent one is a state: the pages -// that would carry a forge link render without it. +// that would carry a forge link render without it. A repository address the +// manifest does not give is the origin remote's on github.com, when it has one: +// a managed repository need carry no package manifest, and the line every page +// opens with links the repository (itd-2610091010348892). func LoadRepoMeta(repoRoot string) (RepoMeta, error) { + m, err := loadPackageMeta(repoRoot) + if err != nil || m.Repository != "" { + return m, err + } + if repo, rerr := ahoy.GitHubRepo(repoRoot); rerr == nil { + m.Repository = "https://github.com/" + repo + } + return m, nil +} + +// loadPackageMeta reads the package manifest alone. +func loadPackageMeta(repoRoot string) (RepoMeta, error) { root, err := os.OpenRoot(repoRoot) if err != nil { return RepoMeta{}, err diff --git a/internal/core/site/compose.go b/internal/core/site/compose.go index 341be377a3..ef801e15cc 100644 --- a/internal/core/site/compose.go +++ b/internal/core/site/compose.go @@ -293,9 +293,22 @@ func (c *composer) headWith(pageTitle, script string) string { b.WriteString(`` + "\n") } b.WriteString("\n\n") + b.WriteString(c.managedLine()) return b.String() } +// managedLine is the line every page opens with: the site is the record of an +// abcd-managed repository, linking to that repository where the build knows its +// address. The words are the allowlist's, so the line adds nothing the +// provenance gate cannot account for. +func (c *composer) managedLine() string { + words := escapeText(c.ui.ManagedRecord) + if c.repo.Repository != "" { + words = `` + words + `` + } + return `

        ` + words + `

        ` +} + // identity reads the canonical Identity block. A repository that records none // renders the page without it rather than failing: the hero's headline and lede // come from the rationale page, and those are what carry the page. diff --git a/internal/core/site/fixture_test.go b/internal/core/site/fixture_test.go index bc04896dad..a844756252 100644 --- a/internal/core/site/fixture_test.go +++ b/internal/core/site/fixture_test.go @@ -349,7 +349,8 @@ func (f *fixture) writeSources() { "matches_system": "matches this computer", "read_script": "read the script", "unreleased": "unreleased", - "beta": "Beta" + "beta": "Beta", + "managed_record": "This is the record of an abcd-managed repository" } `) diff --git a/internal/core/site/managedline_test.go b/internal/core/site/managedline_test.go new file mode 100644 index 0000000000..16ed580875 --- /dev/null +++ b/internal/core/site/managedline_test.go @@ -0,0 +1,69 @@ +package site + +// Every page opens with a line saying it is the record of an abcd-managed +// repository, linking to that repository (itd-2610091010348892). Its words are +// an interface string from the closed allowlist, so the provenance gate still +// refuses any text a repository file does not account for. + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func managedLine(href, words string) string { + return "\n" + `

        ` + words + `

        ` +} + +func TestEveryPageOpensWithTheManagedRepositoryLine(t *testing.T) { + f := newFixture(t) + _, files := buildFixtureAt(t, f, "") + ui, err := LoadUI(f.Root(), "site-src/ui.json") + if err != nil { + t.Fatal(err) + } + if ui.ManagedRecord == "" { + t.Fatal("the allowlist declares no words for the line") + } + want := managedLine("https://example.invalid/fixture/repo", escapeText(ui.ManagedRecord)) + pages := 0 + for rel, data := range files { + if !strings.HasSuffix(rel, ".html") { + continue + } + pages++ + if !strings.Contains(string(data), want) { + t.Errorf("%s does not open with the managed-repository line %q", rel, want) + } + } + if pages < 5 { + t.Fatalf("only %d pages were built", pages) + } +} + +// A managed repository need not carry a package manifest. Its repository is +// then the origin remote on github.com, which is what a release workflow's +// checkout carries, so the line still links it. +func TestTheLineLinksTheOriginWithoutAPackageManifest(t *testing.T) { + f := newFixture(t) + if err := os.Remove(filepath.Join(f.Root(), ".claude-plugin", "plugin.json")); err != nil { + t.Fatal(err) + } + f.gitOut("remote", "add", "origin", "git@github.com:acme/widgets.git") + meta, err := LoadRepoMeta(f.Root()) + if err != nil { + t.Fatal(err) + } + if meta.Repository != "https://github.com/acme/widgets" { + t.Fatalf("repository = %q, want the origin's github.com address", meta.Repository) + } +} + +// With no repository address at all the line still says what the site is. +func TestTheLineStandsWithoutARepositoryAddress(t *testing.T) { + c := &composer{ui: UI{ManagedRecord: "The record."}} + if got := c.managedLine(); got != `

        The record.

        ` { + t.Fatalf("line = %q", got) + } +} diff --git a/internal/core/site/setupsrc/site.css b/internal/core/site/setupsrc/site.css index 0be8ae1d00..3f6d8d1245 100644 --- a/internal/core/site/setupsrc/site.css +++ b/internal/core/site/setupsrc/site.css @@ -110,6 +110,10 @@ th{font-family:"Bricolage Grotesque",sans-serif;font-weight:650;font-size:.82rem that duplication would mean changing the markup, not this rule. */ .sr,th .th-portrait~span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} +/* ---------- the line every page opens with (itd-2610091010348892) ---------- */ +.managed{margin:0;padding:6px 16px;text-align:center;font-size:.8rem;line-height:1.4;color:var(--ink-2);background:var(--ground);border-bottom:1px solid var(--rule)} +.managed a{color:inherit} + /* ---------- site header ---------- */ .site-head{background:var(--ground);border-bottom:1px solid var(--rule)} .site-head .wrap{display:flex;align-items:center;gap:22px;min-height:64px;flex-wrap:wrap} diff --git a/internal/core/site/setupsrc/ui.json b/internal/core/site/setupsrc/ui.json index ca641e62e2..f38a5a9d38 100644 --- a/internal/core/site/setupsrc/ui.json +++ b/internal/core/site/setupsrc/ui.json @@ -103,6 +103,7 @@ "read_script": "read the script", "unreleased": "unreleased", "beta": "Beta", + "managed_record": "This is the record of an abcd-managed repository", "health": { "unresolved": "References a record the tree does not hold", "isolated": "Linked to nothing, and nothing links to it", diff --git a/internal/core/site/testdata/contributors.html b/internal/core/site/testdata/contributors.html index 0210887f64..a81c7bf4a0 100644 --- a/internal/core/site/testdata/contributors.html +++ b/internal/core/site/testdata/contributors.html @@ -11,7 +11,7 @@ -

        Contributors

        Authors of record1

        Authors of recordcommits
        Fixture6

        Human author of record. The human contributor is the author of record and +

        This is the record of an abcd-managed repository

        Contributors

        Authors of record1

        Authors of recordcommits
        Fixture6

        Human author of record. The human contributor is the author of record and is responsible for all assisted output. A trailer is disclosure, never an authorship claim.

        CONTRIBUTING.md

        Assisted-by trailers1

        Assistant:assistant-model-11

        declared no assistance 3

        composed by abcd from record facts 0

        no declaration 1

        diff --git a/internal/core/site/testdata/index.html b/internal/core/site/testdata/index.html index 4bb3c71021..ca237a8620 100644 --- a/internal/core/site/testdata/index.html +++ b/internal/core/site/testdata/index.html @@ -12,7 +12,7 @@ -

        fixture — A Fixture Product

        Who this is for

        A fixture lede with a link, some emphasis, some +

        This is the record of an abcd-managed repository

        fixture — A Fixture Product

        Who this is for

        A fixture lede with a link, some emphasis, some strength, and a code span.

        A fixture tagline for a fixture product.

        A fixture pitch, two clauses long, so the hero has something to carry in its aside.

        A fixture cartoon

        Roles

        The fixture has two roles, and this paragraph introduces them.

        The product thinker

        Product thinker

        The one who holds the why.

        The facilitator

        Technical facilitator

        The one who translates it.

        Artefacts

        A fixture opening paragraph about the artefacts.

        diff --git a/internal/core/site/testdata/record-adr-1.html b/internal/core/site/testdata/record-adr-1.html index d58441c84c..7eb41c1a87 100644 --- a/internal/core/site/testdata/record-adr-1.html +++ b/internal/core/site/testdata/record-adr-1.html @@ -11,5 +11,5 @@ -

        The first decision

        adracceptedadr-1

        The first decision's body, which mentions iss-1 in prose.

        Frontmatter

        idadr-1
        typeadr
        statusaccepted
        date2026-01-02
        created2026-01-05
        entered2026-01-05
        touched2026-01-05

        References2

        mentions3

        +

        This is the record of an abcd-managed repository

        The first decision

        adracceptedadr-1

        The first decision's body, which mentions iss-1 in prose.

        Frontmatter

        idadr-1
        typeadr
        statusaccepted
        date2026-01-02
        created2026-01-05
        entered2026-01-05
        touched2026-01-05

        References2

        mentions3

        diff --git a/internal/core/site/testdata/record-dashboard.html b/internal/core/site/testdata/record-dashboard.html index fede9e7e54..69ab81b593 100644 --- a/internal/core/site/testdata/record-dashboard.html +++ b/internal/core/site/testdata/record-dashboard.html @@ -11,5 +11,5 @@ -
        +

        This is the record of an abcd-managed repository

        diff --git a/internal/core/site/testdata/record-foundations.html b/internal/core/site/testdata/record-foundations.html index 2b45632a11..dd6248ba12 100644 --- a/internal/core/site/testdata/record-foundations.html +++ b/internal/core/site/testdata/record-foundations.html @@ -11,5 +11,5 @@ -
        +

        This is the record of an abcd-managed repository

        diff --git a/internal/core/site/testdata/record-graph.html b/internal/core/site/testdata/record-graph.html index 3413f4cded..d3f72f559b 100644 --- a/internal/core/site/testdata/record-graph.html +++ b/internal/core/site/testdata/record-graph.html @@ -12,5 +12,5 @@ -

        Relationships

        Stand by…
        Browse as a list
        +

        This is the record of an abcd-managed repository

        Relationships

        Stand by…
        Browse as a list
        diff --git a/internal/core/site/testdata/record-itd-2.html b/internal/core/site/testdata/record-itd-2.html index 8a715222b2..da48b4ece9 100644 --- a/internal/core/site/testdata/record-itd-2.html +++ b/internal/core/site/testdata/record-itd-2.html @@ -11,7 +11,7 @@ -

        The Shipped One

        intentshippedminorstandaloneitd-2

        Press Release

        +

        This is the record of an abcd-managed repository

        The Shipped One

        intentshippedminorstandaloneitd-2

        Press Release

        A fixture user opens the page and sees the record. They had been reading frontmatter on a forge; now one page says what the project decided and why. "I stopped guessing," said the fixture user.

        diff --git a/internal/core/site/testdata/record-principle.html b/internal/core/site/testdata/record-principle.html index 755062c617..4bda2926b0 100644 --- a/internal/core/site/testdata/record-principle.html +++ b/internal/core/site/testdata/record-principle.html @@ -11,7 +11,7 @@ -

        One fixture principle

        principleone-fixture-principle

        The rule. A principle carries no frontmatter, so the lint scan cannot see +

        This is the record of an abcd-managed repository

        One fixture principle

        principleone-fixture-principle

        The rule. A principle carries no frontmatter, so the lint scan cannot see it and the site reads the store directly. It sits beside the decisions and is not a missing thing.

        Why. It exercises the frontmatter-free path, mentioning adr-1 in passing.

        mentions1

        diff --git a/internal/core/site/testdata/references.html b/internal/core/site/testdata/references.html index d03b285c0b..375620a401 100644 --- a/internal/core/site/testdata/references.html +++ b/internal/core/site/testdata/references.html @@ -11,7 +11,7 @@ -

        References

        References & sources2

        1. Fenella Quill. 2019. On the making of fixtures. Journal of Invented Sources 4, 2. 11-19. doi:10.5555/fixture.2019.4
        2. Orrin Tamsin and Iris Vole. 2021. Rendering the record. Invented Press, Nowhere. https://example.invalid/rendering-the-record

        .abcd/development/research/references.csl.json

        Inspirations2

        Ideas that shaped the fixture — not code it depends on.

          +

          This is the record of an abcd-managed repository

          References

          References & sources2

          1. Fenella Quill. 2019. On the making of fixtures. Journal of Invented Sources 4, 2. 11-19. doi:10.5555/fixture.2019.4
          2. Orrin Tamsin and Iris Vole. 2021. Rendering the record. Invented Press, Nowhere. https://example.invalid/rendering-the-record

          .abcd/development/research/references.csl.json

          Inspirations2

          Ideas that shaped the fixture — not code it depends on.

          • A first invented inspiration — the shape of the fixture's record.
          • A second invented inspiration — the shape of its pages.
          diff --git a/internal/core/site/ui.go b/internal/core/site/ui.go index 638ec95844..8bfc7c053a 100644 --- a/internal/core/site/ui.go +++ b/internal/core/site/ui.go @@ -84,6 +84,11 @@ type UI struct { // looks like a rendering fault, and one reading "v0.6.1" would be a lie. Unreleased string `json:"unreleased"` Beta string `json:"beta"` + // ManagedRecord is the line every page opens with: the site is the record + // of an abcd-managed repository, and the line links that repository + // (itd-2610091010348892). It says who the content belongs to, because the + // repository is where every word on the page comes from. + ManagedRecord string `json:"managed_record"` } // RecordNav labels the explorer's sub-navigation. diff --git a/site-src/site.css b/site-src/site.css index 0be8ae1d00..3f6d8d1245 100644 --- a/site-src/site.css +++ b/site-src/site.css @@ -110,6 +110,10 @@ th{font-family:"Bricolage Grotesque",sans-serif;font-weight:650;font-size:.82rem that duplication would mean changing the markup, not this rule. */ .sr,th .th-portrait~span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} +/* ---------- the line every page opens with (itd-2610091010348892) ---------- */ +.managed{margin:0;padding:6px 16px;text-align:center;font-size:.8rem;line-height:1.4;color:var(--ink-2);background:var(--ground);border-bottom:1px solid var(--rule)} +.managed a{color:inherit} + /* ---------- site header ---------- */ .site-head{background:var(--ground);border-bottom:1px solid var(--rule)} .site-head .wrap{display:flex;align-items:center;gap:22px;min-height:64px;flex-wrap:wrap} diff --git a/site-src/ui.json b/site-src/ui.json index ca641e62e2..f38a5a9d38 100644 --- a/site-src/ui.json +++ b/site-src/ui.json @@ -103,6 +103,7 @@ "read_script": "read the script", "unreleased": "unreleased", "beta": "Beta", + "managed_record": "This is the record of an abcd-managed repository", "health": { "unresolved": "References a record the tree does not hold", "isolated": "Linked to nothing, and nothing links to it", From 52a12eb24ad5bbae44c0215f674b3c2a1b064adf Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:15:41 +0100 Subject: [PATCH 011/165] feat: add the github-pages hosting adapter A second provider behind the hosting seam: a project's own GitHub Pages, built by its release workflow. The repository half holds no secret: the deploy job runs in the github-pages environment with the run's own token (contents: read, pages: write, id-token: write) and deploys through actions/upload-pages-artifact v5.0.0 and actions/deploy-pages v5.0.1, pinned to full commits. The host half speaks the GitHub REST API through the forge as the person, so it names no credential: it reads whether the repository is public and has a Pages site, creates one built by a workflow, sets a custom domain only when one is asked for, and reports the address Pages gives. A private repository, a site published from a branch and a domain routed elsewhere are refused rather than moved. The seam gains Site.Repo and one method, Profile, carrying the deploy environment and permissions, the switch, the public-only rule and the base path the host serves the site below; cloudflare's profile is the site environment, contents: read and the root. githubpagestest is an in-process fake of the four routes. Assisted-by: Claude:claude-opus-5-5 --- .../adapter/hosting/cloudflare/cloudflare.go | 9 +- .../hosting/cloudflare/cloudflare_test.go | 11 + .../hosting/githubpages/githubpages.go | 277 ++++++++++++++++++ .../hosting/githubpages/githubpages_test.go | 219 ++++++++++++++ .../githubpages/githubpagestest/fake.go | 149 ++++++++++ internal/adapter/hosting/hosting.go | 39 ++- 6 files changed, 699 insertions(+), 5 deletions(-) create mode 100644 internal/adapter/hosting/githubpages/githubpages.go create mode 100644 internal/adapter/hosting/githubpages/githubpages_test.go create mode 100644 internal/adapter/hosting/githubpages/githubpagestest/fake.go diff --git a/internal/adapter/hosting/cloudflare/cloudflare.go b/internal/adapter/hosting/cloudflare/cloudflare.go index cbc68e17dc..23c236222a 100644 --- a/internal/adapter/hosting/cloudflare/cloudflare.go +++ b/internal/adapter/hosting/cloudflare/cloudflare.go @@ -1,4 +1,4 @@ -// Package cloudflare is the one hosting provider abcd ships +// Package cloudflare is the hosting provider abcd's own site deploys through // (itd-2609061543533170): an assets-only Cloudflare Worker, the host abcd's own // site is served from. // @@ -95,6 +95,13 @@ func (Adapter) CredentialName() string { return CredentialName } // Secrets are the deploy environment's secret names. func (Adapter) Secrets() []string { return []string{SecretToken, SecretAccount} } +// Profile: the deploy job runs in the `site` environment holding the two +// secrets, reads the repository only, and deploys every release to a Worker +// served at the root of its address. +func (Adapter) Profile(hosting.Site) hosting.Profile { + return hosting.Profile{Environment: "site", Permissions: []string{"contents: read"}, BasePath: "/"} +} + // DeployStep runs `wrangler deploy` through the pinned action. `deploy` also // asserts the routes wrangler.jsonc declares, so a lost domain attachment is // re-claimed by the next release rather than staying lost. diff --git a/internal/adapter/hosting/cloudflare/cloudflare_test.go b/internal/adapter/hosting/cloudflare/cloudflare_test.go index b7b9a518a3..9e006982cb 100644 --- a/internal/adapter/hosting/cloudflare/cloudflare_test.go +++ b/internal/adapter/hosting/cloudflare/cloudflare_test.go @@ -268,3 +268,14 @@ func TestRepositoryHalf(t *testing.T) { t.Fatalf("the deploy step runs a shell; it should only call the pinned action:\n%s", step) } } + +// TestTheProfileIsTheSecretHoldingSiteEnvironment: the deploy job runs in the +// `site` environment that holds the two secrets, reads the repository only, +// deploys every release and serves the site at the root of its address. +func TestTheProfileIsTheSecretHoldingSiteEnvironment(t *testing.T) { + p := Adapter{}.Profile(hosting.Site{Name: "example-site"}) + if p.Environment != "site" || len(p.Permissions) != 1 || p.Permissions[0] != "contents: read" || + p.Switched || p.PublicOnly || p.BasePath != "/" { + t.Fatalf("profile = %+v", p) + } +} diff --git a/internal/adapter/hosting/githubpages/githubpages.go b/internal/adapter/hosting/githubpages/githubpages.go new file mode 100644 index 0000000000..453b3f53c8 --- /dev/null +++ b/internal/adapter/hosting/githubpages/githubpages.go @@ -0,0 +1,277 @@ +// Package githubpages is the hosting provider that publishes a project's site +// on its own GitHub Pages (itd-2610091010348892): the project's release +// workflow builds the about page and development record at each release, and +// GitHub hosts them at owner.github.io//, or at a custom domain. +// +// The repository half holds no secret. The deploy job runs in the +// `github-pages` environment with the run's own short-lived token and three +// permissions (contents: read, pages: write, id-token: write), and deploys the +// rendered site through actions/upload-pages-artifact and actions/deploy-pages, +// each pinned to a full commit. There is no host configuration file. +// +// The host half speaks the GitHub REST API through the forge, as the person +// who invoked the verb — the way the site setup's forge stage does, so abcd +// holds no GitHub token and resolves no credential: +// +// GET repos/{owner}/{repo} is it public, and has it a Pages site +// GET repos/{owner}/{repo}/pages the site's source, domain and address +// POST repos/{owner}/{repo}/pages create the site, built by a workflow +// PUT repos/{owner}/{repo}/pages route a custom domain to it +// +// These calls are written against GitHub's published REST reference and are +// exercised in this repository only against githubpagestest's in-process fake. +// +// Public repositories only, for now: Pages on a private repository is +// access-controlled only on GitHub's enterprise plan, so a private repository +// is refused rather than published. +package githubpages + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/intentdriven/abcd/internal/adapter/hosting" +) + +// The deploy step's pins: full commits of the two actions' releases, each at +// least two weeks old when it was pinned. +const ( + uploadAction = "actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0" + deployAction = "actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1" +) + +// Environment is the forge environment GitHub Pages deploys through. +const Environment = "github-pages" + +// maxHostMessage bounds how much of a forge error reaches a returned error. +const maxHostMessage = 300 + +// API makes one GitHub REST call through the forge, as the person who invoked +// the verb: method, a repo-relative API path ("repos/acme/widgets/pages") and a +// JSON body or nil. A non-2xx answer is an error naming its HTTP status. +type API func(ctx context.Context, method, path string, body []byte) ([]byte, error) + +// Adapter is the github-pages provider. API is the forge; nil reaches nothing. +type Adapter struct { + API API +} + +var _ hosting.Adapter = Adapter{} + +// Name is the manifest key. +func (Adapter) Name() string { return "github-pages" } + +// CredentialName is empty: the provider acts through the forge as the person. +func (Adapter) CredentialName() string { return "" } + +// Secrets: none. The deploy job uses the run's own token. +func (Adapter) Secrets() []string { return nil } + +// DeployStep hands the unpacked ./site to Pages and deploys it. +func (Adapter) DeployStep() string { + return ` - name: Hand the rendered site to GitHub Pages + uses: ` + uploadAction + ` + with: + path: site + + - name: Deploy to GitHub Pages + id: deployment + uses: ` + deployAction + ` +` +} + +// HostConfig: the provider reads no configuration file. +func (Adapter) HostConfig(hosting.Site) (string, []byte) { return "", nil } + +// Profile: the `github-pages` environment, the run's token with Pages and +// OIDC rights, the switch, public repositories only, and the project path +// GitHub serves the site below. +func (Adapter) Profile(s hosting.Site) hosting.Profile { + return hosting.Profile{ + Environment: Environment, + Permissions: []string{"contents: read", "pages: write", "id-token: write"}, + Switched: true, + PublicOnly: true, + BasePath: basePath(s), + } +} + +// basePath is where GitHub serves the site: the root of a custom domain, the +// root of an owner's own owner.github.io repository, and // for any other +// repository. +func basePath(s hosting.Site) string { + owner, name, ok := splitRepo(s.Repo) + if !ok || s.Domain != "" || strings.EqualFold(name, owner+".github.io") { + return "/" + } + return "/" + name + "/" +} + +// Connect returns the host half. The provider holds no token, so the one it is +// handed is ignored. +func (a Adapter) Connect(string) hosting.Provider { return &client{api: a.API} } + +var ( + // nameRe is a GitHub owner or repository name, as ahoy's remote parser + // accepts one; "." and ".." are refused separately. + nameRe = regexp.MustCompile(`^[A-Za-z0-9_.-]{1,100}$`) + domainRe = regexp.MustCompile(`^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]([a-z0-9-]{0,61}[a-z0-9])?$`) + // addrRe is the address Pages reports: https, a plain host, a plain path. + addrRe = regexp.MustCompile(`^https://[a-z0-9.-]+(/[A-Za-z0-9_.~-]+)*/?$`) +) + +func splitRepo(repo string) (owner, name string, ok bool) { + owner, name, ok = strings.Cut(repo, "/") + for _, p := range []string{owner, name} { + if !ok || !nameRe.MatchString(p) || p == "." || p == ".." { + return "", "", false + } + } + return owner, name, true +} + +func checkSite(s hosting.Site) error { + if _, _, ok := splitRepo(s.Repo); !ok { + return errors.New("github-pages: the site names no plain owner/name repository on GitHub, so nothing is sent") + } + if s.Domain != "" && !domainRe.MatchString(s.Domain) { + return errors.New("github-pages: the domain is not a plain domain name, so it will not be sent") + } + return nil +} + +type client struct { + api API +} + +// call makes one call; what names it in an error. +func (c *client) call(ctx context.Context, method, what, path string, body any) ([]byte, error) { + if c.api == nil { + return nil, fmt.Errorf("github-pages: %s: the forge is not reachable from here", what) + } + var raw []byte + if body != nil { + var err error + if raw, err = json.Marshal(body); err != nil { + return nil, err + } + } + out, err := c.api(ctx, method, path, raw) + if err != nil { + msg := err.Error() + if len(msg) > maxHostMessage { + msg = msg[:maxHostMessage] + "…" + } + return nil, fmt.Errorf("github-pages: %s: %s", what, msg) + } + return out, nil +} + +type pagesSite struct { + BuildType string `json:"build_type"` + CNAME *string `json:"cname"` + HTMLURL string `json:"html_url"` +} + +func (c *client) pages(ctx context.Context, s hosting.Site) (pagesSite, error) { + out, err := c.call(ctx, "GET", "read the Pages site", "repos/"+s.Repo+"/pages", nil) + if err != nil { + return pagesSite{}, err + } + var p pagesSite + if json.Unmarshal(out, &p) != nil { + return pagesSite{}, errors.New("github-pages: read the Pages site: the response is not a Pages site") + } + return p, nil +} + +// Verify: there is no credential to verify; the forge stage proves the forge. +func (c *client) Verify(context.Context) error { + return errors.New("github-pages: the provider acts through the forge as you and keeps no credential to verify") +} + +// Inspect reads whether the repository is public, whether its Pages site +// exists and is built by a workflow, and whether the domain is routed to it. +func (c *client) Inspect(ctx context.Context, s hosting.Site) (hosting.State, error) { + if err := checkSite(s); err != nil { + return hosting.State{}, err + } + out, err := c.call(ctx, "GET", "read the repository", "repos/"+s.Repo, nil) + if err != nil { + return hosting.State{}, err + } + var repo struct { + Private *bool `json:"private"` + HasPages bool `json:"has_pages"` + } + if json.Unmarshal(out, &repo) != nil || repo.Private == nil { + return hosting.State{}, errors.New("github-pages: read the repository: the response does not say whether it is public") + } + if *repo.Private { + return hosting.State{}, fmt.Errorf("github-pages: %s is private, and the project site is published for public repositories only", s.Repo) + } + st := hosting.State{Routed: s.Domain == ""} + if !repo.HasPages { + return st, nil + } + p, err := c.pages(ctx, s) + if err != nil { + return hosting.State{}, err + } + if p.BuildType != "workflow" { + return hosting.State{}, fmt.Errorf("github-pages: %s already publishes GitHub Pages from a branch; abcd will not move a live site "+ + "off it, so set its Pages source to GitHub Actions in the repository's settings and re-run", s.Repo) + } + st.Exists = true + if s.Domain != "" && p.CNAME != nil && *p.CNAME != "" { + if *p.CNAME != s.Domain { + cname := *p.CNAME + if !domainRe.MatchString(cname) { + cname = "a domain with an unprintable name" + } + return hosting.State{}, fmt.Errorf("github-pages: %s already serves its Pages site at %s; abcd will not move a live domain", s.Repo, cname) + } + st.Routed = true + } + return st, nil +} + +// Create creates the Pages site, built by the release workflow. +func (c *client) Create(ctx context.Context, s hosting.Site) error { + if err := checkSite(s); err != nil { + return err + } + _, err := c.call(ctx, "POST", "create the Pages site", "repos/"+s.Repo+"/pages", map[string]string{"build_type": "workflow"}) + return err +} + +// Route sets the custom domain. A site with no domain is a no-op. +func (c *client) Route(ctx context.Context, s hosting.Site) error { + if err := checkSite(s); err != nil { + return err + } + if s.Domain == "" { + return nil + } + _, err := c.call(ctx, "PUT", "route the domain", "repos/"+s.Repo+"/pages", map[string]string{"cname": s.Domain}) + return err +} + +// Address is the address Pages reports for the site. +func (c *client) Address(ctx context.Context, s hosting.Site) (string, error) { + if err := checkSite(s); err != nil { + return "", err + } + p, err := c.pages(ctx, s) + if err != nil { + return "", err + } + if !addrRe.MatchString(p.HTMLURL) { + return "", errors.New("github-pages: the Pages site reports no plain https address") + } + return p.HTMLURL, nil +} diff --git a/internal/adapter/hosting/githubpages/githubpages_test.go b/internal/adapter/hosting/githubpages/githubpages_test.go new file mode 100644 index 0000000000..dedc57975b --- /dev/null +++ b/internal/adapter/hosting/githubpages/githubpages_test.go @@ -0,0 +1,219 @@ +package githubpages + +// The github-pages provider over githubpagestest's in-process fake of the +// GitHub REST API. Nothing here reaches a network. + +import ( + "context" + "errors" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/intentdriven/abcd/internal/adapter/hosting" + "github.com/intentdriven/abcd/internal/adapter/hosting/githubpages/githubpagestest" +) + +func connect(f *githubpagestest.Server) hosting.Provider { return Adapter{API: f.API}.Connect("") } + +var widgets = hosting.Site{Name: "widgets", Repo: "acme/widgets"} + +func TestTheRepositoryHalfHoldsNoSecret(t *testing.T) { + a := Adapter{} + if a.Name() != "github-pages" { + t.Fatalf("name = %q", a.Name()) + } + if a.CredentialName() != "" { + t.Fatalf("credential = %q; the provider acts through the forge as the person and resolves none", a.CredentialName()) + } + if len(a.Secrets()) != 0 { + t.Fatalf("secrets = %v, want none", a.Secrets()) + } + if rel, data := a.HostConfig(widgets); rel != "" || data != nil { + t.Fatalf("host config = %q, want none", rel) + } + step := a.DeployStep() + pinRe := regexp.MustCompile(`uses: actions/(upload-pages-artifact|deploy-pages)@[0-9a-f]{40} # v[0-9]+\.[0-9]+\.[0-9]+\n`) + if got := len(pinRe.FindAllString(step, -1)); got != 2 { + t.Fatalf("the deploy step pins %d of the two Pages actions to a full commit:\n%s", got, step) + } + for _, want := range []string{"path: site\n", "id: deployment\n"} { + if !strings.Contains(step, want) { + t.Errorf("the deploy step lacks %q:\n%s", want, step) + } + } + if strings.Contains(step, "secrets.") || strings.Contains(step, "${{") { + t.Fatalf("the deploy step reads a secret or an expression:\n%s", step) + } +} + +func TestTheProfileIsTheRunsOwnTokenOnly(t *testing.T) { + p := Adapter{}.Profile(widgets) + want := hosting.Profile{ + Environment: "github-pages", + Permissions: []string{"contents: read", "pages: write", "id-token: write"}, + Switched: true, PublicOnly: true, BasePath: "/widgets/", + } + if !reflect.DeepEqual(p, want) { + t.Fatalf("profile = %+v, want %+v", p, want) + } + for _, c := range []struct { + site hosting.Site + base string + }{ + {hosting.Site{Name: "w", Repo: "acme/widgets", Domain: "docs.example.com"}, "/"}, + {hosting.Site{Name: "w", Repo: "Acme/acme.github.io"}, "/"}, + {hosting.Site{Name: "w", Repo: "acme/acme.github.io.old"}, "/acme.github.io.old/"}, + {hosting.Site{Name: "w"}, "/"}, + } { + if got := (Adapter{}).Profile(c.site).BasePath; got != c.base { + t.Errorf("base path for %+v = %q, want %q", c.site, got, c.base) + } + } +} + +func TestInspectCreateAndAddressAProjectSite(t *testing.T) { + f := githubpagestest.New() + p := connect(f) + st, err := p.Inspect(context.Background(), widgets) + if err != nil || st.Exists || !st.Routed { + t.Fatalf("inspect = %+v, %v; want absent and vacuously routed", st, err) + } + if err := p.Create(context.Background(), widgets); err != nil { + t.Fatal(err) + } + if got := f.Repos["acme/widgets"].Pages; got == nil || got.BuildType != "workflow" { + t.Fatalf("pages = %+v, want a site built by the workflow", got) + } + if st, err = p.Inspect(context.Background(), widgets); err != nil || !st.Exists { + t.Fatalf("inspect after create = %+v, %v", st, err) + } + addr, err := p.Address(context.Background(), widgets) + if err != nil || addr != "https://acme.github.io/widgets/" { + t.Fatalf("address = %q, %v", addr, err) + } + if err := p.Route(context.Background(), widgets); err != nil { + t.Fatal(err) + } + for _, c := range f.CallLog() { + if strings.HasPrefix(c, githubpagestest.UpdatePages) { + t.Fatalf("a site with no domain set one: %v", f.CallLog()) + } + } +} + +func TestRouteSetsTheCustomDomain(t *testing.T) { + f := githubpagestest.New() + f.Repos["acme/widgets"].Pages = &githubpagestest.Pages{BuildType: "workflow"} + s := widgets + s.Domain = "docs.example.com" + p := connect(f) + st, err := p.Inspect(context.Background(), s) + if err != nil || !st.Exists || st.Routed { + t.Fatalf("inspect = %+v, %v; want existing and unrouted", st, err) + } + if err := p.Route(context.Background(), s); err != nil { + t.Fatal(err) + } + if got := f.Repos["acme/widgets"].Pages.CNAME; got != "docs.example.com" { + t.Fatalf("cname = %q", got) + } + if st, _ = p.Inspect(context.Background(), s); !st.Routed { + t.Fatal("the domain does not read as routed after Route") + } + if addr, err := p.Address(context.Background(), s); err != nil || addr != "https://docs.example.com/" { + t.Fatalf("address = %q, %v", addr, err) + } +} + +func TestInspectRefuses(t *testing.T) { + for name, c := range map[string]struct { + repo githubpagestest.Repo + site hosting.Site + want string + }{ + "a private repository": {githubpagestest.Repo{Private: true}, widgets, "public repositories only"}, + "a site built from a branch": {githubpagestest.Repo{Pages: &githubpagestest.Pages{BuildType: "legacy"}}, widgets, + "from a branch"}, + "a domain routed elsewhere": {githubpagestest.Repo{Pages: &githubpagestest.Pages{BuildType: "workflow", CNAME: "other.example.com"}}, + hosting.Site{Name: "widgets", Repo: "acme/widgets", Domain: "docs.example.com"}, "other.example.com"}, + } { + t.Run(name, func(t *testing.T) { + f := githubpagestest.New() + r := c.repo + f.Repos["acme/widgets"] = &r + _, err := connect(f).Inspect(context.Background(), c.site) + if err == nil || !strings.Contains(err.Error(), c.want) { + t.Fatalf("inspect = %v, want a refusal naming %q", err, c.want) + } + }) + } +} + +func TestEveryForgeFailureIsReported(t *testing.T) { + boom := errors.New("exit status 1 (gh: Server Error (HTTP 500))") + for _, route := range []string{githubpagestest.GetRepo, githubpagestest.GetPages, githubpagestest.CreatePages, githubpagestest.UpdatePages} { + t.Run(route, func(t *testing.T) { + f := githubpagestest.New() + f.Repos["acme/widgets"].Pages = &githubpagestest.Pages{BuildType: "workflow"} + if route == githubpagestest.CreatePages { + f.Repos["acme/widgets"].Pages = nil + } + f.Fail[route] = boom + s := widgets + s.Domain = "docs.example.com" + p := connect(f) + var err error + switch route { + case githubpagestest.GetRepo, githubpagestest.GetPages: + _, err = p.Inspect(context.Background(), s) + case githubpagestest.CreatePages: + err = p.Create(context.Background(), s) + case githubpagestest.UpdatePages: + err = p.Route(context.Background(), s) + } + if err == nil || !strings.Contains(err.Error(), "HTTP 500") || !strings.HasPrefix(err.Error(), "github-pages: ") { + t.Fatalf("%s failure = %v", route, err) + } + }) + } +} + +func TestAnUnsafeSiteIsRefusedBeforeACall(t *testing.T) { + for _, s := range []hosting.Site{ + {Name: "w"}, + {Name: "w", Repo: "acme"}, + {Name: "w", Repo: "acme/widgets/extra"}, + {Name: "w", Repo: "acme/../widgets"}, + {Name: "w", Repo: "acme/wid gets"}, + {Name: "w", Repo: "acme/widgets", Domain: "https://x.example.com"}, + } { + f := githubpagestest.New() + p := connect(f) + if _, err := p.Inspect(context.Background(), s); err == nil { + t.Errorf("inspect accepted %+v", s) + } + if err := p.Create(context.Background(), s); err == nil { + t.Errorf("create accepted %+v", s) + } + if n := len(f.CallLog()); n != 0 { + t.Errorf("%+v reached the forge %d times", s, n) + } + } +} + +func TestAnAddressThatIsNotAPlainHTTPSURLIsRefused(t *testing.T) { + p := Adapter{API: func(context.Context, string, string, []byte) ([]byte, error) { + return []byte(`{"build_type":"workflow","html_url":"javascript:alert(1)"}`), nil + }}.Connect("") + if addr, err := p.Address(context.Background(), widgets); err == nil { + t.Fatalf("address = %q, want a refusal", addr) + } +} + +func TestWithoutTheForgeNothingIsCalled(t *testing.T) { + if _, err := (Adapter{}).Connect("").Inspect(context.Background(), widgets); err == nil { + t.Fatal("a provider with no forge inspected the site") + } +} diff --git a/internal/adapter/hosting/githubpages/githubpagestest/fake.go b/internal/adapter/hosting/githubpages/githubpagestest/fake.go new file mode 100644 index 0000000000..342acdc8e6 --- /dev/null +++ b/internal/adapter/hosting/githubpages/githubpagestest/fake.go @@ -0,0 +1,149 @@ +// Package githubpagestest is an in-process fake of the slice of the GitHub +// REST API the github-pages hosting adapter calls, for tests only. The adapter +// speaks through the forge the way the site setup's forge stage does — `gh api` +// as the person who invoked the verb, never a token abcd holds — so the fake +// answers the adapter's API calls directly rather than over a socket, and +// fails them the way `gh api` does: an error naming the HTTP status. +// +// It answers exactly these routes, and anything else is a 404: +// +// GET repos/{owner}/{repo} the repository: private, has_pages +// GET repos/{owner}/{repo}/pages the Pages site: build_type, cname, html_url +// POST repos/{owner}/{repo}/pages create the Pages site +// PUT repos/{owner}/{repo}/pages change its source or custom domain +package githubpagestest + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "sync" +) + +// Route keys name a route for Fail and for the call log. +const ( + GetRepo = "GET repo" + GetPages = "GET pages" + CreatePages = "POST pages" + UpdatePages = "PUT pages" +) + +// Pages is one repository's Pages site. +type Pages struct { + BuildType string + CNAME string +} + +// Repo is one repository the fake holds. +type Repo struct { + Private bool + // Pages is nil while the repository has no Pages site. + Pages *Pages +} + +// Server is the fake. Its fields are the forge's state; lock Mu to read them +// while a call may be in flight. +type Server struct { + Mu sync.Mutex + Repos map[string]*Repo + // Fail makes a route answer with this error. + Fail map[string]error + // Calls logs each call as " ". + Calls []string +} + +// New is a fake holding one public repository, acme/widgets, with no Pages +// site yet. +func New() *Server { + return &Server{Repos: map[string]*Repo{"acme/widgets": {}}, Fail: map[string]error{}} +} + +// CallLog is a copy of the calls made so far. +func (s *Server) CallLog() []string { + s.Mu.Lock() + defer s.Mu.Unlock() + return append([]string(nil), s.Calls...) +} + +// status is the error `gh api` returns for an HTTP status. +func status(code int, text string) error { + return fmt.Errorf("exit status 1 (gh: %s (HTTP %d))", text, code) +} + +// API answers one call, shaped as the adapter's API. +func (s *Server) API(_ context.Context, method, path string, body []byte) ([]byte, error) { + s.Mu.Lock() + defer s.Mu.Unlock() + rest, ok := strings.CutPrefix(path, "repos/") + if !ok { + return nil, status(404, "Not Found") + } + parts := strings.Split(rest, "/") + key := "" + switch { + case len(parts) == 2 && method == "GET": + key = GetRepo + case len(parts) == 3 && parts[2] == "pages": + key = method + " pages" + default: + return nil, status(404, "Not Found") + } + s.Calls = append(s.Calls, strings.TrimSpace(key+" "+string(body))) + if err := s.Fail[key]; err != nil { + return nil, err + } + full := parts[0] + "/" + parts[1] + repo, ok := s.Repos[full] + if !ok { + return nil, status(404, "Not Found") + } + switch key { + case GetRepo: + return json.Marshal(map[string]any{"full_name": full, "private": repo.Private, "has_pages": repo.Pages != nil}) + case GetPages: + if repo.Pages == nil { + return nil, status(404, "Not Found") + } + addr := "https://" + strings.ToLower(parts[0]) + ".github.io/" + parts[1] + "/" + if repo.Pages.CNAME != "" { + addr = "https://" + repo.Pages.CNAME + "/" + } + return json.Marshal(map[string]any{"build_type": repo.Pages.BuildType, "cname": nilIfEmpty(repo.Pages.CNAME), "html_url": addr}) + case CreatePages: + if repo.Pages != nil { + return nil, status(409, "GitHub Pages is already enabled.") + } + var req struct { + BuildType string `json:"build_type"` + } + if json.Unmarshal(body, &req) != nil || req.BuildType == "" { + return nil, status(422, "Invalid request.") + } + repo.Pages = &Pages{BuildType: req.BuildType} + return []byte("{}"), nil + case UpdatePages: + if repo.Pages == nil { + return nil, status(404, "Not Found") + } + var req map[string]*string + if json.Unmarshal(body, &req) != nil { + return nil, status(422, "Invalid request.") + } + if v, ok := req["cname"]; ok && v != nil { + repo.Pages.CNAME = *v + } + if v, ok := req["build_type"]; ok && v != nil { + repo.Pages.BuildType = *v + } + return nil, nil + } + return nil, status(404, "Not Found") +} + +func nilIfEmpty(s string) any { + if s == "" { + return nil + } + return s +} diff --git a/internal/adapter/hosting/hosting.go b/internal/adapter/hosting/hosting.go index 920ff33208..c858284607 100644 --- a/internal/adapter/hosting/hosting.go +++ b/internal/adapter/hosting/hosting.go @@ -4,9 +4,10 @@ // the core consumes the interface, never a vendor, and a provider is one // implementation of it. // -// One provider ships (cloudflare, the host abcd's own site uses). A second is a -// new implementation of Adapter and one line in the core's provider list, never -// a change to the verb. +// Two providers ship: cloudflare, the host abcd's own site uses, and +// github-pages, a project's own GitHub Pages, built by its release workflow +// (itd-2610091010348892). Another is a new implementation of Adapter and one +// line in the core's provider list, never a change to the verb. // // The seam has two halves because a provider touches the site twice: // @@ -20,6 +21,9 @@ // // The credential is handed to Connect and lives only inside the returned // Provider. No method returns it, no error carries it, and nothing writes it. +// A provider that names no credential (CredentialName is empty) acts through +// the forge as the person who invoked the verb, the way the forge stage does, +// and Connect is handed nothing. package hosting import "context" @@ -30,6 +34,30 @@ import "context" type Site struct { Name string Domain string + // Repo is the forge repository the site is built from, as owner/name, + // when the caller knows it. A provider that hosts the site on the forge + // itself acts on it; the others ignore it. + Repo string +} + +// Profile is what the verb and the workflow it writes need to know about a +// provider beyond its deploy step. +type Profile struct { + // Environment is the forge environment the deploy job runs in. + Environment string + // Permissions are the deploy job's token permissions, one `scope: level` + // each, in the order the workflow lists them. + Permissions []string + // Switched: the provider publishes only while the composition's + // hosting.publish switch is on. Setup asks for the switch, and the + // workflow publishes nothing while it is off. + Switched bool + // PublicOnly: the provider serves public repositories only, so setup + // refuses a private one before it writes anything. + PublicOnly bool + // BasePath is the path the site is served below on the host, with a + // leading and a trailing slash; "/" is the root. + BasePath string } // State is what the host holds for a site, read without writing anything. @@ -72,8 +100,11 @@ type Adapter interface { // the secrets named by Secrets and nothing else. DeployStep() string // HostConfig is the committed configuration file the deploy step reads, - // repo-relative, and its bytes for s. + // repo-relative, and its bytes for s; an empty path when the provider + // reads none. HostConfig(s Site) (rel string, data []byte) + // Profile is the provider's facts for s. + Profile(s Site) Profile // Connect returns a Provider acting with token. The token stays inside it. Connect(token string) Provider } From cf20d3ded4dd8189953089a2e038ff92a9a98e97 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:28:10 +0100 Subject: [PATCH 012/165] feat: wire abcd session serve and blueprint `abcd session serve` runs the relay in the foreground on loopback by default; a non-loopback --listen is the explicit second choice, and --behind-proxy is for the team relay. The start line prints the Tailscale Serve command, never Funnel. The sign-in secret is read from ABCD_SESSION_GITHUB_CLIENT_SECRET by name and never printed. `abcd session blueprint` writes .abcd/relay/ pinned to a release's checksum, read through the release-asset fetcher. The listener gate becomes a two-entry allowlist, the dashboard and the session relay, each citing adr-2610032150581128 and its amendment. The verb is placed under Set-up beside dashboard, with its plugin page, brief surface chapter, sentences, surface snapshot and CLI reference. Assisted-by: Claude:claude-opus-5-5 --- .../brief/04-surfaces/37-session.md | 140 ++++++++++ .abcd/development/brief/04-surfaces/README.md | 10 +- .abcd/development/release/surface.json | 57 ++++ .abcd/rules.json | 1 + commands/session.md | 105 +++++++ docs/reference/cli/commands.md | 72 +++++ internal/core/surface/sentences.go | 7 + internal/surface/cli/barerender.go | 3 + internal/surface/cli/cli.go | 1 + internal/surface/cli/helpgroups.go | 1 + internal/surface/cli/helpgroups_test.go | 2 +- internal/surface/cli/session.go | 217 +++++++++++++++ internal/surface/cli/session_surface_test.go | 258 ++++++++++++++++++ .../dashboard/listener_boundary_test.go | 44 ++- internal/surface/session/listen.go | 85 ++++++ internal/surface/session/server.go | 4 +- 16 files changed, 988 insertions(+), 19 deletions(-) create mode 100644 .abcd/development/brief/04-surfaces/37-session.md create mode 100644 commands/session.md create mode 100644 internal/surface/cli/session.go create mode 100644 internal/surface/cli/session_surface_test.go create mode 100644 internal/surface/session/listen.go diff --git a/.abcd/development/brief/04-surfaces/37-session.md b/.abcd/development/brief/04-surfaces/37-session.md new file mode 100644 index 0000000000..e26492f0b2 --- /dev/null +++ b/.abcd/development/brief/04-surfaces/37-session.md @@ -0,0 +1,140 @@ +# `/abcd:session` — The Brief Session's Relay + +`/abcd:session` runs the relay a team's live brief session goes through, and +writes the blueprint that deploys it as the team's own relay +(itd-2610091016262335, spc-2610141709430146 steps 1 and 2). abcd runs no relay +and holds no key +([adr-2610091016270711](../../decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md)): +the team runs it, on the host's own machine or on the team's own Render +account. It is abcd's second listener, admitted on the terms of +[adr-2610032150581128](../../decisions/adrs/2610032150581128-abcd-is-reachable-on-a-network-only.md)'s +amendment of 2026-10-14 and brief invariant 7's inbound clause +([`02-constraints/03-invariants.md`](../02-constraints/03-invariants.md)). + +This chapter describes the relay and its blueprint. The session page that +connects to it, and the route back to GitHub, are the spec's later steps. + +## Sub-verbs + +> _Machine-checked (`surface_coverage`, spc-27): each row records the verb's +> adr-40 bucket (`lint` / `review` / `audit` / `gate`, or `—` for a +> non-assessment verb) and its existence (`shipped` / `staged`). The existence +> fact is verified against the committed command-tree snapshot in both +> directions. The bucket cell is checked for membership of the closed adr-40 +> vocabulary only: the snapshot carries no bucket field, so a bucket that is +> wrong but legal passes, and that cell stays a review-grain claim._ + +| Verb | Bucket | Status | +|---|---|---| +| `serve` | — | shipped | +| `blueprint` | — | shipped | + +## Where the relay listens + +The relay runs in the foreground until interrupted and listens on loopback +(`127.0.0.1:8787`) unless the host names another address on that run. A +non-loopback address is the second, explicit choice the amendment requires, +and is meant for the team's relay, which listens on every address behind its +host's proxy and trusts that proxy's forwarded address. On a person's own +machine the relay is reached through Tailscale Serve, on the tailnet only, and +the start line prints the Serve command that does it. Funnel is never offered: +a guest outside the tailnet joins through the team's relay. + +It serves four routes and nothing else: the session's WebSocket at `/session`, +the sign-in exchange at `/auth/token`, the sign-in app's public client id at +`/config`, and a health check at `/healthz`. A browser is admitted only from +`https://abcdesign.app`, a loopback page or another origin the host names, each +an exact origin; a WebSocket or exchange with no origin, or another one, is +refused with 403. + +## Who may do what + +A connection is a guest until GitHub says otherwise. The host signs in, and +GitHub must report that the login may push to the repository before a session +opens; the relay answers with a six-character join code drawn from a +cryptographic source over 31 symbols with the look-alike characters left out. +The host can draw a new code at any time, and the old one stops working. A +host whose page reloads keeps the role by signing in as the same login. + +Anyone with the code joins as a guest and receives the session's update log +replayed, then every update as it arrives. A guest's update frame is refused, +which is how a guest stays read-only on the text; a guest's `suggest` message +is routed to the team members alone, stamped with the guest's display name, +cleaned of control and format characters. A participant who presents a GitHub +token, signed in or pasted, becomes a team member when GitHub reports push +access, and the token is dropped straight after the check. Team members' +updates are logged and forwarded; awareness (cursors and selections) is +forwarded from anyone and never logged. A team member registers the Yjs client +ids their page edits under, which the roster carries for credit. + +Twenty wrong codes from one address within ten minutes shut that address out +of joining until the window passes; an IPv6 visitor is counted by its /64, and +the forwarded address is trusted only behind a declared proxy. + +## What it holds, and for how long + +Updates are opaque bytes behind a one-byte tag; the relay never parses them or +the brief. Everything is in memory: a session is dropped whole when the host +closes it, or when nobody has been connected for thirty minutes. Nothing is +written to disk and nothing is logged, no token, secret or brief text. The +sign-in app's secret is read from `ABCD_SESSION_GITHUB_CLIENT_SECRET` by name, +added only to the relay's own exchange with GitHub, and never sent to a +browser; GitHub's answer is passed on field by field, so nothing it echoes +reaches the page. + +Limits: 256 KiB a message, 8 MiB of log a session, 32 participants a session, +16 sessions a relay, five sign-in attempts a connection, and a connection that +falls a thousand frames behind or misses a ping is dropped. + +## The blueprint + +The blueprint is `.abcd/relay/render.yaml`, one free Render web service with +the docker runtime, a `/healthz` health check, automatic deploys off (a +redeploy ends every open session) and the sign-in app's two variables +unsynced, and `.abcd/relay/Dockerfile`, which downloads the named release's +`abcd-linux-amd64` and checks it against the SHA-256 that release's +`checksums.txt` publishes, read when the files are written and pinned in the +file, before running the relay on every address behind Render's proxy. Each +file's first line carries a digest of its body, so a +rerun replaces a file left as abcd wrote it and refuses, writing nothing, one +that was edited or that abcd did not write. It refuses outside a checkout abcd +manages and for a release that publishes no checksum for the linux/amd64 +binary. + +## Exit codes + +`0` done; `1` a failure after the relay started; `2` refused, with nothing +started or written. + + + +## Appendix: the shipped surface + +_Generated from the command tree; a drift test fails `go test` when this appendix and the tree disagree. It lists flags and sub-verbs only. What each flag means is in the [CLI reference](../../../../docs/reference/cli/commands.md), and exit codes, output fields and behaviour are the prose's to state._ + +### `abcd session` + +Sub-verbs: `abcd session blueprint`, `abcd session serve`. + +Flags: none. + +### `abcd session blueprint` + +Sub-verbs: none. + +| Flag | Type | +|---|---| +| `--release` | string | + +### `abcd session serve` + +Sub-verbs: none. + +| Flag | Type | +|---|---| +| `--behind-proxy` | bool | +| `--client-id` | string | +| `--listen` | string | +| `--origin` | stringArray | + + diff --git a/.abcd/development/brief/04-surfaces/README.md b/.abcd/development/brief/04-surfaces/README.md index 3b9a4f657e..36cfcc44a8 100644 --- a/.abcd/development/brief/04-surfaces/README.md +++ b/.abcd/development/brief/04-surfaces/README.md @@ -44,6 +44,7 @@ are wiring rather than user-facing surface are listed separately under | 34 | `/abcd:build` | shipped | Start the loop that takes one READY intent to delivered, refusing while a question is open or a peer holds it | [`34-build.md`](34-build.md) | | 35 | `/abcd:drain` | shipped | Work the open ledger unattended: preview with `--dry-run` which open issues a machine may fix alone and in what order, then run it one lane at a time, routing every hand-back by its kind | [`35-drain.md`](35-drain.md) | | 36 | `/abcd:dashboard` | shipped | Open the project from a phone or another computer on your own Tailscale network, through a server that answers only the devices Tailscale names | [`36-dashboard.md`](36-dashboard.md) | +| 37 | `/abcd:session` | shipped | Run the relay a team's live brief session goes through, on the host's machine over Tailscale Serve or on the team's own relay, and write that relay's blueprint | [`37-session.md`](37-session.md) | ## How much of this table a machine keeps honest @@ -138,7 +139,7 @@ the command's sentence (the section below). | Block | Group | Verbs | |---|---|---| -| people | set-up | `ahoy`, `dashboard`, `update`, and the framework's `help` and `completion` | +| people | set-up | `ahoy`, `dashboard`, `session`, `update`, and the framework's `help` and `completion` | | people | records | `build`, `capture`, `decide`, `ideate`, `intent`, `library` | | people | checks | `lab`, `reading` | | people | portability | `disembark`, `embark` | @@ -219,8 +220,9 @@ read-only render of their own state, and close on the next move where there is one to name. It is a convention rather than a universal, and the exceptions are where the -tree does not yet meet its own discipline. Seven parents print usage with no state -at all: `disembark`, `docs`, `embark`, `guard`, `history`, `ideate`, and `scribe`. Bare +tree does not yet meet its own discipline. Eight parents print usage with no state +at all: `disembark`, `docs`, `embark`, `guard`, `history`, `ideate`, `scribe`, and +`session`, whose running relay keeps its sessions in that process's memory alone. Bare `abcd launch` refuses with a hint to pass `--dry-run`. Bare `abcd decide` refuses because its one operand is the quoted title it mints a record from, and bare `abcd build` refuses as a usage error because its one operand is the intent it @@ -339,7 +341,7 @@ documents is then an unknown command (iss-161). One file per verb, directly unde `abcd`, `ahoy`, `banlist`, `build`, `capture`, `dashboard`, `decide`, `disembark`, `docs`, `drain`, `embark`, `guard`, `history`, `ideate`, `identity`, `implement`, `inbox`, `intent`, `lab`, `launch`, `library`, `lint`, `mode`, `peers`, -`reading`, `reflect`, `report`, `scribe`, `site`, `update`. +`reading`, `reflect`, `report`, `scribe`, `session`, `site`, `update`. `abcd.md` is the bare `/abcd` status board; every other file is `/abcd:`. diff --git a/.abcd/development/release/surface.json b/.abcd/development/release/surface.json index 2450e4a9a2..221f8430aa 100644 --- a/.abcd/development/release/surface.json +++ b/.abcd/development/release/surface.json @@ -3306,6 +3306,63 @@ } ] }, + { + "path": "abcd session", + "hidden": false, + "group": "set-up", + "block": "people", + "sentence": "Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb.", + "flags": [] + }, + { + "path": "abcd session blueprint", + "hidden": false, + "sentence": "Write the team relay's Render blueprint and Dockerfile, pinned to a release's checksum: Writes .abcd/relay/; refuses an edited file.", + "flags": [ + { + "name": "release", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + } + ] + }, + { + "path": "abcd session serve", + "hidden": false, + "sentence": "Run the brief session relay in the foreground, on loopback by default: Writes nothing; refuses a --listen that is not an IP address and port.", + "flags": [ + { + "name": "behind-proxy", + "shorthand": "", + "type": "bool", + "required": false, + "hidden": false + }, + { + "name": "client-id", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + }, + { + "name": "listen", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + }, + { + "name": "origin", + "shorthand": "", + "type": "stringArray", + "required": false, + "hidden": false + } + ] + }, { "path": "abcd site", "hidden": false, diff --git a/.abcd/rules.json b/.abcd/rules.json index 4daff63008..09bc326bd0 100644 --- a/.abcd/rules.json +++ b/.abcd/rules.json @@ -84,6 +84,7 @@ "report", "rules", "scribe", + "session", "site", "spec", "statusline", diff --git a/commands/session.md b/commands/session.md new file mode 100644 index 0000000000..ea0032e4e8 --- /dev/null +++ b/commands/session.md @@ -0,0 +1,105 @@ +--- +name: session +description: "Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb." +argument-hint: "[serve|blueprint]" +block: people +--- + +# `/abcd:session` + +The relay a team's live brief session goes through. During a call, a team +opens its brief on abcdesign.app, the host opens a session, and everyone else +joins by a QR code or a six-character code. The page is static; the session +itself runs on a relay the team runs, never on anything abcd runs. The relay +holds each session's edits in memory only, as opaque bytes, and drops them +when the host closes the session or nobody has been connected for half an +hour. It writes nothing to disk and logs nothing: no token, no secret, no +brief text. + +There are two ways to run it, both the team's own: + +- **On the host's own machine**, with `serve`, reached by teammates on the + host's Tailscale network through Tailscale Serve. Never Funnel: a guest + outside the tailnet joins through the team's relay instead. +- **On the team's own relay**, deployed once to the team's own Render account + from the files `blueprint` writes. One deploy serves all of the team's + sessions. + +## Status + +Bare invocation prints the two sub-verbs and writes nothing. A running relay's +sessions live in that process's memory alone, so there is no state to report. + +```bash +"${CLAUDE_PLUGIN_ROOT}/abcd" session --help +``` + +## Serve + +Starting the relay opens a network listener, so do it only when the user asks +to host a session from this computer. It runs in the foreground until stopped +with Ctrl-C, and every session it holds ends with it, so the user runs it in a +Terminal of their own rather than in this session: + +```bash +abcd session serve +``` + +It listens on this computer alone (`127.0.0.1:8787`) and prints one line +saying where, with the `tailscale serve --bg ` command that puts it on +the user's tailnet at an https address; relay that line as printed. A +non-loopback `--listen` is a second, explicit choice, meant for the team's +relay; pass it only when the user names the address. `--behind-proxy` trusts +the proxy's `X-Forwarded-For` entry as each visitor's address, and belongs to +the team's relay behind its host's proxy. `--origin` admits a page served from +another origin besides `https://abcdesign.app` and loopback. `--json` prints +the start line as an object with `url`, `loopback`, `behind_proxy`, `sign_in` +and `hint`. + +Team members sign in with the team's own GitHub sign-in app. The relay reads +its secret from `ABCD_SESSION_GITHUB_CLIENT_SECRET` by name, and its public +client id from `--client-id` or `ABCD_SESSION_GITHUB_CLIENT_ID`. Never ask the +user for the secret, and never print it or pass it on a command line. Without +them, sign-in is not offered and a team member pastes a personal access token +instead; the relay asks GitHub for that token's login and whether it may push +to the repository, and drops the token straight after. + +It refuses (exit 2, nothing started) a `--listen` that is not an IP address +(or `localhost`) and a port, an `--origin` that is not an exact +`scheme://host[:port]`, and an address it cannot listen on. + +## Blueprint + +```bash +"${CLAUDE_PLUGIN_ROOT}/abcd" session blueprint --release +``` + +It writes `.abcd/relay/render.yaml`, a Render Blueprint declaring one free web +service, and `.abcd/relay/Dockerfile`, which downloads the named abcd +release's linux/amd64 binary and checks it against the SHA-256 that release's +`checksums.txt` publishes. It reads that checksum from the release page when it +writes the files (the one network read it makes, named on stderr) and pins it +in the Dockerfile. `--release` defaults to this abcd's own version when that is +a release. A file left exactly as abcd wrote it is replaced; one that was +edited, or that abcd did not write, is never overwritten, and then nothing is +written (exit 2). It refuses outside a checkout abcd manages, and for a +release that publishes no checksum for the linux/amd64 binary. + +Relay what it prints: each file and whether it was written, replaced or left +unchanged, the release and checksum pinned, and the next steps. The user +commits both files, chooses New, then Blueprint, on Render, picks the +repository, sets the Blueprint path to `.abcd/relay/render.yaml`, and sets +`ABCD_SESSION_GITHUB_CLIENT_ID` and `ABCD_SESSION_GITHUB_CLIENT_SECRET` in the +service's Environment. `--json` returns `release`, `asset`, `sha256`, +`checksums_url` and `files`, each with its `path` and `state`. + +**Binary resolution.** Run `"${CLAUDE_PLUGIN_ROOT}/abcd"` — a plugin install +provisions the binary into the plugin root, so this is the rung that fires for a +plugin user. If that path does not exist, try `abcd` on `PATH`; if that fails +too, you are in a source checkout of this repo, where — and only there — +`go run ./cmd/abcd` works, the published payload carrying no `cmd/`. To put a +binary on `PATH`, run `ahoy install` through whichever rung just resolved: +`"${CLAUDE_PLUGIN_ROOT}/abcd" ahoy install`, `abcd ahoy install`, or +`go run ./cmd/abcd ahoy install` in a source checkout. + +**User input:** $ARGUMENTS diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md index 567ee17442..37eb66ebf0 100644 --- a/docs/reference/cli/commands.md +++ b/docs/reference/cli/commands.md @@ -3712,6 +3712,78 @@ beside the run, write-once; an ingest that lands no record leaves the run open. abcd scribe ingest --scribe-json scribe.json --dispositions dispositions.md ``` +### `abcd session` + +Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb. + +**Usage:** `abcd session` + +Run the relay a team's live brief session goes through, or write the blueprint +that deploys it to the team's own Render account. The relay holds each session's +edits in memory only, as opaque bytes, and drops them when the host closes the +session or nobody has been connected for half an hour; it writes nothing to disk +and logs nothing. abcd runs no relay of its own: the team runs it, on the host's +machine reached over Tailscale Serve, or on the team's own relay. + +Bare `abcd session` prints this help and writes nothing. + +#### `abcd session blueprint` + +Write the team relay's Render blueprint and Dockerfile, pinned to a release's checksum: Writes .abcd/relay/; refuses an edited file. + +**Usage:** `abcd session blueprint [flags]` + +Write the team's own relay deployment into this checkout: .abcd/relay/render.yaml, +a Render Blueprint declaring one free web service, and .abcd/relay/Dockerfile, +which downloads the released abcd binary for linux/amd64 and checks it against +the SHA-256 that release's checksums.txt publishes. The checksum is read from the +release page when the files are written (the one network read this verb makes, +named on stderr) and pinned in the Dockerfile. + +--release names the release to pin; it defaults to this abcd's own version when +that is a release. A file left exactly as abcd wrote it is replaced; one that was +edited, or that abcd did not write, is never overwritten, and nothing is written. + +Exit 2 on a refusal, with nothing written. + +**Flags:** + +``` + --release string the abcd release tag to pin, as vMAJOR.MINOR.PATCH (default: this abcd's own version, when it is a release) +``` + +#### `abcd session serve` + +Run the brief session relay in the foreground, on loopback by default: Writes nothing; refuses a --listen that is not an IP address and port. + +**Usage:** `abcd session serve [flags]` + +Run the brief session relay in the foreground until interrupted. It listens on +this computer alone (127.0.0.1:8787) unless --listen names another address, +which is the second, explicit choice: the team's own relay deployment listens on +0.0.0.0 behind its host's proxy, with --behind-proxy. On a person's own machine, +teammates reach it through Tailscale Serve, on the tailnet only; the start line +prints the command. Never Funnel: a guest outside the tailnet joins through the +team's relay instead. + +It serves the session WebSocket at /session, the sign-in exchange at /auth/token, +the sign-in app's public client id at /config and a health check at /healthz. It +admits a browser only from https://abcdesign.app, a loopback page or an --origin. +The team's GitHub sign-in app secret is read from ABCD_SESSION_GITHUB_CLIENT_SECRET +and its client id from --client-id or ABCD_SESSION_GITHUB_CLIENT_ID; without +them, sign-in is not offered and a team member pastes a personal access token. + +Exit 2 on a refusal, with nothing started. + +**Flags:** + +``` + --behind-proxy trust the last X-Forwarded-For entry as the client's address; only for the team relay behind its host's proxy + --client-id string the team's GitHub sign-in app's public client id (default: ABCD_SESSION_GITHUB_CLIENT_ID) + --listen string the address to listen on: an IP address (or localhost) and a port; anything but loopback is listening beyond this computer (default "127.0.0.1:8787") + --origin stringArray another page origin to admit, as scheme://host[:port] (repeatable); https://abcdesign.app and loopback are always admitted +``` + ### `abcd site` Report what the website declares and what was built: Writes nothing; refuses any argument. diff --git a/internal/core/surface/sentences.go b/internal/core/surface/sentences.go index 856536df08..9b2903d602 100644 --- a/internal/core/surface/sentences.go +++ b/internal/core/surface/sentences.go @@ -228,6 +228,13 @@ var sentences = map[string]string{ "abcd dashboard stop": "Stop the dashboard after checking its process is the one start launched: " + "Writes the run file's removal; refuses when another start or stop holds the lock.", + "abcd session": "Run the relay a team's live brief session goes through, or write its deployment blueprint: " + + "Writes nothing bare; refuses an unknown sub-verb.", + "abcd session blueprint": "Write the team relay's Render blueprint and Dockerfile, pinned to a release's checksum: " + + "Writes .abcd/relay/; refuses an edited file.", + "abcd session serve": "Run the brief session relay in the foreground, on loopback by default: " + + "Writes nothing; refuses a --listen that is not an IP address and port.", + "abcd inbox": "List the reports managed repositories filed back to abcd, newest first: " + "Writes nothing; refuses any argument.", "abcd inbox promote": "File one report as a capture in abcd's own ledger: " + diff --git a/internal/surface/cli/barerender.go b/internal/surface/cli/barerender.go index c0ccd36bc4..7027fe3e09 100644 --- a/internal/surface/cli/barerender.go +++ b/internal/surface/cli/barerender.go @@ -43,6 +43,9 @@ var bareRenderExceptions = map[string]string{ "a terminal and refuses (exit 2) anywhere else", "scribe": "a parent whose sub-verbs act on a named reading run or on a scribe's " + "returned output; with no operand it has no state of its own to render, so bare prints its sub-verbs", + "session": "a parent of serve, which runs the relay in the foreground, and blueprint, " + + "which writes the team relay's files; a running relay's sessions live in that " + + "process's memory alone, so there is no state to render and bare prints its sub-verbs", "statusline": "its row belongs to a managed repository: there bare renders it with or " + "without the payload (TestStatuslineEmptyStdinStillRendersTheBadge), and anywhere else, " + "this test's scratch repository included, abcd has no row and bare prints nothing of " + diff --git a/internal/surface/cli/cli.go b/internal/surface/cli/cli.go index 470a2173dd..771a9f8b58 100644 --- a/internal/surface/cli/cli.go +++ b/internal/surface/cli/cli.go @@ -404,6 +404,7 @@ func NewRootCommand() *cobra.Command { root.AddCommand(newReportCommand(&asJSON)) root.AddCommand(newInboxCommand(&asJSON)) root.AddCommand(newDashboardCommand(&asJSON)) + root.AddCommand(newSessionCommand(&asJSON)) root.AddCommand(newStatuslineCommand(&asJSON)) root.AddCommand(newAhoyCommand(&asJSON)) diff --git a/internal/surface/cli/helpgroups.go b/internal/surface/cli/helpgroups.go index 12a9f8c7d8..f6c3dbc178 100644 --- a/internal/surface/cli/helpgroups.go +++ b/internal/surface/cli/helpgroups.go @@ -109,6 +109,7 @@ var helpPlacements = map[string]helpPlacement{ // The person's groups. "ahoy": {group: groupSetUp}, "dashboard": {group: groupSetUp}, + "session": {group: groupSetUp}, "update": {group: groupSetUp}, "build": {group: groupRecords}, "capture": {group: groupRecords}, diff --git a/internal/surface/cli/helpgroups_test.go b/internal/surface/cli/helpgroups_test.go index fd4556d943..880c1f4700 100644 --- a/internal/surface/cli/helpgroups_test.go +++ b/internal/surface/cli/helpgroups_test.go @@ -86,7 +86,7 @@ func TestRootHelpListsThePersonsGroups(t *testing.T) { // The product thinker's placements of 2026-10-09 (P1 to P6 in // spc-2610100613109045's audit) and the spec's decision 1 on the groups. for group, want := range map[string][]string{ - "Set-up:": {"ahoy", "completion", "dashboard", "help", "update"}, + "Set-up:": {"ahoy", "completion", "dashboard", "help", "session", "update"}, "Records:": {"build", "capture", "decide", "ideate", "intent", "library"}, "Checks:": {"lab", "reading"}, "Portability:": {"disembark", "embark"}, diff --git a/internal/surface/cli/session.go b/internal/surface/cli/session.go new file mode 100644 index 0000000000..00c2672652 --- /dev/null +++ b/internal/surface/cli/session.go @@ -0,0 +1,217 @@ +package cli + +import ( + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/intentdriven/abcd/internal/core" + "github.com/intentdriven/abcd/internal/core/ahoy" + coresession "github.com/intentdriven/abcd/internal/core/session" + "github.com/intentdriven/abcd/internal/core/update" + "github.com/intentdriven/abcd/internal/gitutil" + "github.com/intentdriven/abcd/internal/surface/session" + "github.com/intentdriven/abcd/internal/termsafe" + "github.com/spf13/cobra" +) + +// The environment variables the relay reads the team's GitHub sign-in app +// from. The secret is read by name and never printed; the client id is public. +const ( + sessionSecretEnv = "ABCD_SESSION_GITHUB_CLIENT_SECRET" + sessionClientIDEnv = "ABCD_SESSION_GITHUB_CLIENT_ID" +) + +// sessionReleaseOrigin is where the blueprint reads a release's checksums. +const sessionReleaseOrigin = "https://github.com/intentdriven/abcd" + +// newSessionReleaseFetcher builds the fetcher the blueprint reads a release's +// checksums.txt through. It is a package var so a test serves the release from +// memory: no test reaches the network. +var newSessionReleaseFetcher = func() (coresession.AssetFetcher, error) { + return update.NewReleaseAssets(sessionReleaseOrigin) +} + +// newSessionCommand builds the `session` verb, the front door onto the brief +// session's relay (itd-2610091016262335, spc-2610141709430146 steps 1 and 2). +func newSessionCommand(asJSON *bool) *cobra.Command { + cmd := &cobra.Command{ + Use: "session", + Long: "Run the relay a team's live brief session goes through, or write the blueprint\n" + + "that deploys it to the team's own Render account. The relay holds each session's\n" + + "edits in memory only, as opaque bytes, and drops them when the host closes the\n" + + "session or nobody has been connected for half an hour; it writes nothing to disk\n" + + "and logs nothing. abcd runs no relay of its own: the team runs it, on the host's\n" + + "machine reached over Tailscale Serve, or on the team's own relay.\n\n" + + "Bare `abcd session` prints this help and writes nothing.", + Args: cobra.NoArgs, + RunE: helpRunE, + } + cmd.AddCommand(newSessionServeCommand(asJSON), newSessionBlueprintCommand(asJSON)) + return cmd +} + +// sessionStart is the one line, or JSON object, serve prints once it listens. +type sessionStart struct { + URL string `json:"url"` + Loopback bool `json:"loopback"` + Proxy bool `json:"behind_proxy"` + SignIn bool `json:"sign_in"` + Hint string `json:"hint"` +} + +func newSessionServeCommand(asJSON *bool) *cobra.Command { + var listen, clientID string + var origins []string + var behindProxy bool + c := &cobra.Command{ + Use: "serve", + Long: "Run the brief session relay in the foreground until interrupted. It listens on\n" + + "this computer alone (" + session.DefaultListen + ") unless --listen names another address,\n" + + "which is the second, explicit choice: the team's own relay deployment listens on\n" + + "0.0.0.0 behind its host's proxy, with --behind-proxy. On a person's own machine,\n" + + "teammates reach it through Tailscale Serve, on the tailnet only; the start line\n" + + "prints the command. Never Funnel: a guest outside the tailnet joins through the\n" + + "team's relay instead.\n\n" + + "It serves the session WebSocket at /session, the sign-in exchange at /auth/token,\n" + + "the sign-in app's public client id at /config and a health check at /healthz. It\n" + + "admits a browser only from https://abcdesign.app, a loopback page or an --origin.\n" + + "The team's GitHub sign-in app secret is read from " + sessionSecretEnv + "\n" + + "and its client id from --client-id or " + sessionClientIDEnv + "; without\n" + + "them, sign-in is not offered and a team member pastes a personal access token.\n\n" + + "Exit 2 on a refusal, with nothing started.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + const prefix = "abcd session serve: " + addr, err := session.ParseListen(listen) + if err != nil { + return &exitError{Code: 2, Msg: prefix + err.Error() + " (nothing started)"} + } + if clientID == "" { + clientID = strings.TrimSpace(os.Getenv(sessionClientIDEnv)) + } + secret := os.Getenv(sessionSecretEnv) + srv, err := session.New(session.Config{ + Origins: origins, + BehindProxy: behindProxy, + ClientID: clientID, + ClientSecret: secret, + }) + secret = "" + _ = secret + if err != nil { + return &exitError{Code: 2, Msg: prefix + "--origin " + err.Error() + " (nothing started)"} + } + ln, err := session.Listen(addr) + if err != nil { + return &exitError{Code: 2, Msg: prefix + "cannot listen on " + addr.String() + ": " + err.Error() + " (nothing started)"} + } + bound := ln.Addr().String() + loopback := addr.Addr().IsLoopback() + start := sessionStart{ + URL: "http://" + bound, + Loopback: loopback, + Proxy: behindProxy, + SignIn: clientID != "" && os.Getenv(sessionSecretEnv) != "", + } + if loopback { + port := bound[strings.LastIndex(bound, ":")+1:] + start.Hint = "teammates on your tailnet reach it once you run `tailscale serve --bg " + port + + "`; open the session page with the https address that prints" + } else { + start.Hint = "listening beyond this computer, on an address chosen with --listen" + } + _ = render(cmd.OutOrStdout(), *asJSON, start, func(w io.Writer) { + where := "this computer alone (loopback)" + if !loopback { + where = "every address --listen names, beyond this computer" + } + fmt.Fprintf(w, "abcd session serve: relay listening at %s, on %s\n", termsafe.Sanitize(start.URL), where) + fmt.Fprintf(w, " %s\n", start.Hint) + if start.SignIn { + fmt.Fprintln(w, " sign-in: the team's GitHub sign-in app is set up") + } else { + fmt.Fprintf(w, " sign-in is not set up (set %s and --client-id); a team member pastes a personal access token instead\n", sessionSecretEnv) + } + fmt.Fprintln(w, " stop it with Ctrl-C; every session it holds ends with it") + }) + if err := srv.Run(cmd.Context(), ln); err != nil && !errors.Is(err, cmd.Context().Err()) { + return fmt.Errorf("%s%w", prefix, err) + } + return nil + }, + } + c.Flags().StringVar(&listen, "listen", session.DefaultListen, "the address to listen on: an IP address (or localhost) and a port; anything but loopback is listening beyond this computer") + c.Flags().BoolVar(&behindProxy, "behind-proxy", false, "trust the last X-Forwarded-For entry as the client's address; only for the team relay behind its host's proxy") + c.Flags().StringArrayVar(&origins, "origin", nil, "another page origin to admit, as scheme://host[:port] (repeatable); https://abcdesign.app and loopback are always admitted") + c.Flags().StringVar(&clientID, "client-id", "", "the team's GitHub sign-in app's public client id (default: "+sessionClientIDEnv+")") + return c +} + +func newSessionBlueprintCommand(asJSON *bool) *cobra.Command { + var release string + c := &cobra.Command{ + Use: "blueprint", + Long: "Write the team's own relay deployment into this checkout: .abcd/relay/render.yaml,\n" + + "a Render Blueprint declaring one free web service, and .abcd/relay/Dockerfile,\n" + + "which downloads the released abcd binary for linux/amd64 and checks it against\n" + + "the SHA-256 that release's checksums.txt publishes. The checksum is read from the\n" + + "release page when the files are written (the one network read this verb makes,\n" + + "named on stderr) and pinned in the Dockerfile.\n\n" + + "--release names the release to pin; it defaults to this abcd's own version when\n" + + "that is a release. A file left exactly as abcd wrote it is replaced; one that was\n" + + "edited, or that abcd did not write, is never overwritten, and nothing is written.\n\n" + + "Exit 2 on a refusal, with nothing written.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + const prefix = "abcd session blueprint: " + if release == "" { + release = core.Version + if !coresession.ValidReleaseTag(release) { + return &exitError{Code: 2, Msg: prefix + "this abcd (" + release + ") is not a release; name one with --release vMAJOR.MINOR.PATCH (nothing written)"} + } + } + if !coresession.ValidReleaseTag(release) { + return &exitError{Code: 2, Msg: prefix + strconv.Quote(release) + " is not a release tag; name one as vMAJOR.MINOR.PATCH (nothing written)"} + } + cwd, err := os.Getwd() + if err != nil { + return err + } + root, err := gitutil.CheckoutRoot(cwd, "the relay blueprint's checkout") + if err != nil { + return &exitError{Code: 2, Msg: prefix + err.Error() + " (nothing written)"} + } + if !ahoy.Managed(root) { + return &exitError{Code: 2, Msg: prefix + "this checkout is not one abcd manages; run `abcd ahoy install` first (nothing written)"} + } + fetch, err := newSessionReleaseFetcher() + if err != nil { + return fmt.Errorf("%s%w", prefix, err) + } + fmt.Fprintf(cmd.ErrOrStderr(), "%sreading release %s's checksums.txt from %s\n", prefix, termsafe.Sanitize(release), sessionReleaseOrigin) + res, err := coresession.WriteBlueprint(coresession.BlueprintRequest{Root: root, Release: release, Fetch: fetch}) + if err != nil { + var ref *coresession.BlueprintRefusal + if errors.As(err, &ref) || res.Digest == "" { + return &exitError{Code: 2, Msg: prefix + err.Error()} + } + return fmt.Errorf("%s%w", prefix, err) + } + return render(cmd.OutOrStdout(), *asJSON, res, func(w io.Writer) { + for _, f := range res.Files { + fmt.Fprintf(w, "abcd session blueprint: %s %s\n", f.State, f.Path) + } + fmt.Fprintf(w, " pinned: abcd %s, %s sha256 %s\n", termsafe.Sanitize(res.Release), res.Asset, res.Digest) + fmt.Fprintf(w, " from: %s\n", termsafe.Sanitize(res.Source)) + fmt.Fprintln(w, " next: commit both files; on Render choose New, then Blueprint, pick this repository and set the Blueprint path to .abcd/relay/render.yaml;") + fmt.Fprintf(w, " then set %s and %s in the service's Environment\n", sessionClientIDEnv, sessionSecretEnv) + }) + }, + } + c.Flags().StringVar(&release, "release", "", "the abcd release tag to pin, as vMAJOR.MINOR.PATCH (default: this abcd's own version, when it is a release)") + return c +} diff --git a/internal/surface/cli/session_surface_test.go b/internal/surface/cli/session_surface_test.go new file mode 100644 index 0000000000..1245f2a8da --- /dev/null +++ b/internal/surface/cli/session_surface_test.go @@ -0,0 +1,258 @@ +package cli + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + coresession "github.com/intentdriven/abcd/internal/core/session" +) + +// sessionDigest is the checksum the fake release publishes. +const sessionDigest = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + +// fakeSessionRelease serves a release's checksums.txt from memory: no test +// reaches the network. +type fakeSessionRelease struct{ asked []string } + +func (f *fakeSessionRelease) FetchReleaseAsset(tag, name string) ([]byte, string, bool, error) { + f.asked = append(f.asked, tag+"/"+name) + url := "https://github.com/intentdriven/abcd/releases/download/" + tag + "/" + name + if tag == "v9.9.9" { + return nil, url, false, nil + } + return []byte(sessionDigest + " abcd-linux-amd64\n"), url, true, nil +} + +func swapSessionRelease(t *testing.T) *fakeSessionRelease { + t.Helper() + f := &fakeSessionRelease{} + old := newSessionReleaseFetcher + newSessionReleaseFetcher = func() (coresession.AssetFetcher, error) { return f, nil } + t.Cleanup(func() { newSessionReleaseFetcher = old }) + return f +} + +// managedSessionRepo is a checkout abcd manages, as the working directory. +func managedSessionRepo(t *testing.T) string { + t.Helper() + home := t.TempDir() + t.Setenv("HOME", home) + repo := filepath.Join(home, "repo") + gitInitAt(t, repo) + if err := os.WriteFile(filepath.Join(repo, "AGENTS.md"), []byte("\n\n"), 0o644); err != nil { + t.Fatal(err) + } + t.Chdir(repo) + return repo +} + +func TestSessionBlueprintWritesThePinnedFiles(t *testing.T) { + repo := managedSessionRepo(t) + rel := swapSessionRelease(t) + out := string(runCLI(t, "session", "blueprint", "--release", "v0.14.1")) + for _, want := range []string{".abcd/relay/Dockerfile", ".abcd/relay/render.yaml", "v0.14.1", sessionDigest, "checksums.txt", "Blueprint path"} { + if !strings.Contains(out, want) { + t.Errorf("output does not say %q:\n%s", want, out) + } + } + if strings.Contains(strings.ToLower(out), "funnel") { + t.Errorf("output mentions Funnel:\n%s", out) + } + docker, err := os.ReadFile(filepath.Join(repo, ".abcd", "relay", "Dockerfile")) + if err != nil || !bytes.Contains(docker, []byte(sessionDigest+" /usr/local/bin/abcd")) { + t.Fatalf("Dockerfile: %v\n%s", err, docker) + } + if strings.Join(rel.asked, ",") != "v0.14.1/checksums.txt" { + t.Fatalf("fetched %v", rel.asked) + } + + // --json puts the result alone on stdout; the fetch is named on stderr. + root := NewRootCommand() + var stdout, stderr bytes.Buffer + root.SetOut(&stdout) + root.SetErr(&stderr) + root.SetArgs([]string{"session", "blueprint", "--release", "v0.14.1", "--json"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + var res coresession.BlueprintResult + if err := json.Unmarshal(stdout.Bytes(), &res); err != nil { + t.Fatalf("%v\n%s", err, stdout.String()) + } + if !strings.Contains(stderr.String(), "checksums.txt") { + t.Errorf("stderr does not name the fetch: %q", stderr.String()) + } + if res.Digest != sessionDigest || len(res.Files) != 2 || res.Files[0].State != "unchanged" { + t.Fatalf("--json = %+v", res) + } +} + +func TestSessionBlueprintRefusals(t *testing.T) { + repo := managedSessionRepo(t) + swapSessionRelease(t) + + // This test binary is no release, so the release must be named. + out, err := runCLIStdinErr(t, "", "session", "blueprint") + if exitCode(err) != 2 || !strings.Contains(err.Error(), "--release") { + t.Fatalf("no --release on a dev build: %v\n%s", err, out) + } + for _, args := range [][]string{ + {"session", "blueprint", "--release", "latest"}, + {"session", "blueprint", "--release", "v9.9.9"}, + } { + if _, err := runCLIStdinErr(t, "", args...); exitCode(err) != 2 { + t.Errorf("%v: %v, want exit 2", args, err) + } + } + if _, err := os.Stat(filepath.Join(repo, ".abcd")); err == nil { + t.Fatal("a refusal created .abcd/") + } + + runCLI(t, "session", "blueprint", "--release", "v0.14.1") + yaml := filepath.Join(repo, ".abcd", "relay", "render.yaml") + if err := os.WriteFile(yaml, []byte("services: []\n"), 0o644); err != nil { + t.Fatal(err) + } + _, err = runCLIStdinErr(t, "", "session", "blueprint", "--release", "v0.14.2") + if exitCode(err) != 2 || !strings.Contains(err.Error(), "render.yaml") { + t.Fatalf("an edited render.yaml: %v, want exit 2 naming it", err) + } + + // Outside a checkout abcd manages. + plain := t.TempDir() + gitInitAt(t, plain) + t.Chdir(plain) + if _, err := runCLIStdinErr(t, "", "session", "blueprint", "--release", "v0.14.1"); exitCode(err) != 2 { + t.Fatalf("an unmanaged checkout: %v, want exit 2", err) + } +} + +// syncBuffer is a writer a running server and a test read at once. +type syncBuffer struct { + mu sync.Mutex + b bytes.Buffer +} + +func (s *syncBuffer) Write(p []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.b.Write(p) +} + +func (s *syncBuffer) String() string { + s.mu.Lock() + defer s.mu.Unlock() + return s.b.String() +} + +// startServe runs `abcd session serve` with args until the test ends, and +// returns its output so far once it is listening. +func startServe(t *testing.T, args ...string) (*syncBuffer, func() error) { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + root := NewRootCommand() + out := &syncBuffer{} + root.SetOut(out) + root.SetErr(out) + root.SetArgs(append([]string{"session", "serve"}, args...)) + done := make(chan error, 1) + go func() { done <- root.ExecuteContext(ctx) }() + stop := func() error { + cancel() + select { + case err := <-done: + return err + case <-time.After(10 * time.Second): + return errors.New("serve did not stop") + } + } + t.Cleanup(func() { _ = stop() }) + deadline := time.Now().Add(5 * time.Second) + for !strings.Contains(out.String(), "http://") { + select { + case err := <-done: + t.Fatalf("serve ended: %v\n%s", err, out.String()) + default: + } + if time.Now().After(deadline) { + t.Fatalf("serve never said where it listens:\n%s", out.String()) + } + time.Sleep(10 * time.Millisecond) + } + return out, stop +} + +func TestSessionServeListensOnLoopbackAndStops(t *testing.T) { + t.Setenv("ABCD_SESSION_GITHUB_CLIENT_SECRET", "test-secret-value-never-printed") + out, stop := startServe(t, "--listen", "127.0.0.1:0", "--client-id", "Iv1.publicid", "--json") + var started struct { + URL string `json:"url"` + Loopback bool `json:"loopback"` + SignIn bool `json:"sign_in"` + Hint string `json:"hint"` + } + if err := json.NewDecoder(strings.NewReader(out.String())).Decode(&started); err != nil { + t.Fatalf("the start line is not JSON: %v\n%s", err, out.String()) + } + if !started.Loopback || !started.SignIn || !strings.HasPrefix(started.URL, "http://127.0.0.1:") { + t.Fatalf("start = %+v", started) + } + if !strings.Contains(started.Hint, "tailscale serve") || strings.Contains(strings.ToLower(started.Hint), "funnel") { + t.Fatalf("hint = %q, want Serve and never Funnel", started.Hint) + } + resp, err := http.Get(started.URL + "/healthz") + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("healthz = %d", resp.StatusCode) + } + if err := stop(); err != nil { + t.Fatalf("stop: %v", err) + } + if strings.Contains(out.String(), "test-secret-value-never-printed") { + t.Fatalf("the secret reached the output:\n%s", out.String()) + } + if _, err := http.Get(started.URL + "/healthz"); err == nil { + t.Fatal("the relay still answers after it stopped") + } +} + +func TestSessionServeTextNamesTheTailnetRoute(t *testing.T) { + t.Setenv("ABCD_SESSION_GITHUB_CLIENT_SECRET", "") + out, stop := startServe(t, "--listen", "127.0.0.1:0") + _ = stop() + text := out.String() + for _, want := range []string{"loopback", "tailscale serve --bg", "sign-in is not set up"} { + if !strings.Contains(text, want) { + t.Errorf("output does not say %q:\n%s", want, text) + } + } + if strings.Contains(strings.ToLower(text), "funnel") { + t.Errorf("output mentions Funnel:\n%s", text) + } +} + +func TestSessionServeRefusals(t *testing.T) { + for _, args := range [][]string{ + {"session", "serve", "--listen", "nonsense"}, + {"session", "serve", "--listen", "example.com:80"}, + {"session", "serve", "--listen", "127.0.0.1:99999"}, + {"session", "serve", "--origin", "*"}, + {"session", "serve", "extra"}, + } { + if _, err := runCLIStdinErr(t, "", args...); exitCode(err) != 2 { + t.Errorf("%v: %v, want exit 2", args, err) + } + } +} diff --git a/internal/surface/dashboard/listener_boundary_test.go b/internal/surface/dashboard/listener_boundary_test.go index c84feae53c..d70ac1c675 100644 --- a/internal/surface/dashboard/listener_boundary_test.go +++ b/internal/surface/dashboard/listener_boundary_test.go @@ -13,11 +13,13 @@ import ( "testing" ) -// The dashboard is the only listener abcd has (adr-2610032150581128). The -// scanner below holds the tree to it, in the shape of the home resolver's -// TestOnlyTheHomeResolverNamesTheHome: it parses every non-test Go file under -// internal/ and cmd/ (testdata skipped) and reports every call that opens a -// listener. Only this package may make one. +// abcd has two listeners, each admitted by adr-2610032150581128: the +// dashboard, and the brief session's relay, which the record's amendment of +// 2026-10-14 admits as the second. The scanner below holds the tree to them, +// in the shape of the home resolver's TestOnlyTheHomeResolverNamesTheHome: it +// parses every non-test Go file under internal/ and cmd/ (testdata skipped) +// and reports every call that opens a listener. Only the two allowed packages +// may make one. // listenerFuncs are the package functions that open a listener, by import // path: a call through any import name of the package is judged. @@ -35,8 +37,22 @@ var listenerFuncs = map[string][]string{ // are called on: (*net.ListenConfig).Listen, (*http.Server).ListenAndServe. var listenerMethods = []string{"Listen", "ListenPacket", "ListenAndServe", "ListenAndServeTLS", "ServeTLS"} -// listenerPackage is the one package allowed to open a listener. -const listenerPackage = "internal/surface/dashboard" +// listenerPackages are the packages allowed to open a listener, each with the +// record that admits it. +var listenerPackages = map[string]string{ + "internal/surface/dashboard": "adr-2610032150581128", + "internal/surface/session": "adr-2610032150581128, as amended 2026-10-14", +} + +// allowedListenerPackage reports the allowed package rel lies in, if any. +func allowedListenerPackage(rel string) (string, bool) { + for pkg := range listenerPackages { + if strings.HasPrefix(rel, pkg+"/") { + return pkg, true + } + } + return "", false +} // listenerTestDoubles are non-test files outside the package that open a // listener for tests alone, each with its reason; the test also proves no @@ -130,11 +146,11 @@ func TestOnlyTheDashboardOpensAListener(t *testing.T) { t.Fatalf("the scan read %d files; it is not looking at the tree", len(files)) } var bad []string - judged := 0 + judged := map[string]int{} for rel, f := range files { calls := listenerCalls(fset, f) - if strings.HasPrefix(rel, listenerPackage+"/") { - judged += len(calls) + if pkg, ok := allowedListenerPackage(rel); ok { + judged[pkg] += len(calls) continue } if _, ok := listenerTestDoubles[rel]; ok { @@ -144,12 +160,14 @@ func TestOnlyTheDashboardOpensAListener(t *testing.T) { bad = append(bad, fmt.Sprintf("%s:%d: %s", rel, c.line, c.call)) } } - if judged == 0 { - t.Fatal("the scan found no listener in the dashboard package; it is not looking at the calls") + for pkg := range listenerPackages { + if judged[pkg] == 0 { + t.Fatalf("the scan found no listener in %s; it is not looking at the calls", pkg) + } } sort.Strings(bad) for _, b := range bad { - t.Errorf("%s opens a listener outside %s: the dashboard is the only listener abcd has (adr-2610032150581128)", b, listenerPackage) + t.Errorf("%s opens a listener outside the dashboard and the session relay: they are the only listeners abcd has (adr-2610032150581128 and its amendment of 2026-10-14)", b) } // A test double's package reaches the binary only through an import from // a non-test file; there must be none. diff --git a/internal/surface/session/listen.go b/internal/surface/session/listen.go new file mode 100644 index 0000000000..381b0bf379 --- /dev/null +++ b/internal/surface/session/listen.go @@ -0,0 +1,85 @@ +package session + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "net" + "net/http" + "net/netip" + "strconv" + "strings" + "time" +) + +// DefaultListen is where the relay listens unless --listen names another +// address: this computer alone (adr-2610032150581128, as amended 2026-10-14). +const DefaultListen = "127.0.0.1:8787" + +// The plain-HTTP bounds; a WebSocket is bounded by its pings once upgraded. +const ( + readHeaderTimeout = 10 * time.Second + idleTimeout = 60 * time.Second + maxHeaderBytes = 16 << 10 + shutdownWait = 5 * time.Second +) + +// ParseListen reads a --listen value: an IP address, or localhost, and a +// port. Port 0 lets the system choose one. A host name other than localhost +// is refused: the relay listens on an address, never on whatever a name +// resolves to. +func ParseListen(s string) (netip.AddrPort, error) { + host, port, err := net.SplitHostPort(strings.TrimSpace(s)) + if err != nil { + return netip.AddrPort{}, fmt.Errorf("--listen %q is not host:port", s) + } + n, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return netip.AddrPort{}, fmt.Errorf("--listen %q: the port is not 0 to 65535", s) + } + if strings.EqualFold(host, "localhost") { + host = "127.0.0.1" + } + a, err := netip.ParseAddr(host) + if err != nil || a.Zone() != "" { + return netip.AddrPort{}, fmt.Errorf("--listen %q: give an IP address (or localhost), not a name", s) + } + return netip.AddrPortFrom(a.Unmap(), uint16(n)), nil +} + +// Listen opens the relay's listener: abcd's second, admitted by +// adr-2610032150581128's amendment of 2026-10-14. +func Listen(addr netip.AddrPort) (net.Listener, error) { + return net.Listen("tcp", addr.String()) +} + +// Run serves the relay on ln until ctx ends, purging idle sessions as it +// goes. The server's own error log goes nowhere: the relay logs nothing. +func (s *Server) Run(ctx context.Context, ln net.Listener) error { + srv := &http.Server{ + Handler: s, + ReadHeaderTimeout: readHeaderTimeout, + IdleTimeout: idleTimeout, + MaxHeaderBytes: maxHeaderBytes, + ErrorLog: log.New(io.Discard, "", 0), + BaseContext: func(net.Listener) context.Context { return ctx }, + } + go s.Sweep(ctx, SweepInterval) + served := make(chan error, 1) + go func() { served <- srv.Serve(ln) }() + select { + case <-ctx.Done(): + shut, cancel := context.WithTimeout(context.Background(), shutdownWait) + defer cancel() + _ = srv.Shutdown(shut) + <-served + return nil + case err := <-served: + if errors.Is(err, http.ErrServerClosed) { + return nil + } + return err + } +} diff --git a/internal/surface/session/server.go b/internal/surface/session/server.go index a4fbcb5e7e..30a4194993 100644 --- a/internal/surface/session/server.go +++ b/internal/surface/session/server.go @@ -245,7 +245,9 @@ func (s *Server) serveSession(w http.ResponseWriter, r *http.Request) { return } ws.SetReadLimit(int64(s.relay.Limits().MaxMessageBytes)) - ctx, cancel := context.WithCancel(context.Background()) + // The request's context ends when the server shuts down (Run's + // BaseContext), which ends every session connection with it. + ctx, cancel := context.WithCancel(r.Context()) defer cancel() p := &peer{ws: ws, out: make(chan outFrame, sendQueue), cancel: cancel} conn := s.relay.Connect(p, clientAddress(r, s.cfg.BehindProxy)) From 806732a6631a565d6cce87ab3c55f8a5bb30ab12 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:28:17 +0100 Subject: [PATCH 013/165] docs: document the brief session relay's two routes A how-to for running the relay on the host's machine through Tailscale Serve or as the team's own relay on Render, with the GitHub sign-in app's setup. Acknowledges coder/websocket and the rascals join pattern the relay follows. Assisted-by: Claude:claude-opus-5-5 --- ACKNOWLEDGEMENTS.md | 15 ++++ docs/how-to/README.md | 1 + docs/how-to/run-a-brief-session-relay.md | 108 +++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 docs/how-to/run-a-brief-session-relay.md diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index fc74c5ac91..7039089b02 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -338,6 +338,21 @@ Ideas and methodologies that shaped the design — not code abcd depends on. dashboard's writes pass through: over plain HTTP it judges a request's Origin against its Host, with no token to keep. +- **coder/websocket (Coder, ISC, )** — + the WebSocket library the brief session's relay speaks through + (`internal/surface/session`, spc-2610141709430146 step 1): one module with + no dependencies of its own. Like golang.org/x/term it is named here rather + than in `go.mod` alone, because the product thinker approved it by name as + the one dependency the relay adds. +- **The rascals demo repository's join pattern** — the precedent for how a + person joins a brief session and how a team runs its relay + (itd-2610091016262335, adr-2610091016270711): a six-character code from a + 31-symbol alphabet with the look-alike characters left out, drawn from a + cryptographic source; wrong codes limited per address, an IPv6 visitor + counted by its /64, and the forwarded address trusted only behind a + declared proxy; and one free Render web service, deployed from a Blueprint + with its secret unsynced, serving every room. `internal/core/session` and + `abcd session blueprint` follow it. - **TruffleHog (Truffle Security)** — the optional deeper secret scanner the `scan.deep` recommendation keys on when the binary is present (`internal/core/ahoy`); integrated as an opt-in engine, never bundled. diff --git a/docs/how-to/README.md b/docs/how-to/README.md index 4a148b67ff..cfeed340b0 100644 --- a/docs/how-to/README.md +++ b/docs/how-to/README.md @@ -6,6 +6,7 @@ goal for a user who already knows the basics (e.g. "install abcd into a repo", `../explanation/`. - [`install.md`](install.md): Install abcd as a plugin, as a command-line binary, or from source. +- [`run-a-brief-session-relay.md`](run-a-brief-session-relay.md): Run the relay a team's live brief session goes through, on your own machine over Tailscale or on the team's own relay on Render. - [`upgrade-to-v0.12.0.md`](upgrade-to-v0.12.0.md): Upgrade to v0.12.0: the loop's renamed stage fields and the removed command spellings, each with its replacement. - [`upgrade-to-v0.13.0.md`](upgrade-to-v0.13.0.md): Upgrade to v0.13.0: rename abcd's folder from `~/.abcd` to `~/.abcd.noindex` in a plain Terminal window, reconnect its worktrees, recover when both folders exist, and refresh each managed project's block. - [`upgrade-to-v0.14.0.md`](upgrade-to-v0.14.0.md): Upgrade to v0.14.0: replace each retired command with its successor, move memory and source commands under library, read the release cut from the launch preview, refresh each managed project's pre-commit hook, and find the agents' commands. diff --git a/docs/how-to/run-a-brief-session-relay.md b/docs/how-to/run-a-brief-session-relay.md new file mode 100644 index 0000000000..c7a54f1ca1 --- /dev/null +++ b/docs/how-to/run-a-brief-session-relay.md @@ -0,0 +1,108 @@ +# Run a brief session relay + +A team edits its brief together live through a relay the team runs itself. +abcd runs no relay and holds no key: the relay is the `abcd session serve` +command, run either on the host's own machine or as the team's own small web +service. Either way it keeps each session's edits in memory only, drops them +when the host closes the session or nobody has been connected for half an +hour, writes nothing to disk and logs nothing. + +Pick one route: + +- [On your own machine, over Tailscale](#on-your-own-machine-over-tailscale): + nothing to deploy, and every participant must be on your Tailscale network. +- [On the team's own relay, on Render](#on-the-teams-own-relay-on-render): one + deploy serves all of the team's sessions, and anyone with the join code can + reach it. + +Both routes use the same [GitHub sign-in app](#set-up-github-sign-in), which is +optional. + +## On your own machine, over Tailscale + +1. Start the relay in a Terminal window of its own: + + ```bash + abcd session serve + ``` + + It listens on this computer alone, at `127.0.0.1:8787`, and prints the next + command. It runs until you press Ctrl-C, and every session it holds ends + then. + +2. Put it on your tailnet with Tailscale Serve, in a second window: + + ```bash + tailscale serve --bg 8787 + ``` + + Tailscale prints the https address teammates on your tailnet reach the relay + at. Use Serve only, never Funnel: Funnel would put your computer on the open + internet. A guest outside your tailnet joins through the team's relay + instead. + +3. When the session is over, stop the relay with Ctrl-C and stop serving with + `tailscale serve --https=443 off`. + +## On the team's own relay, on Render + +1. In the project's checkout, write the blueprint: + + ```bash + abcd session blueprint --release v0.14.1 + ``` + + Name the abcd release the relay should run. The command writes + `.abcd/relay/render.yaml` and `.abcd/relay/Dockerfile`. The Dockerfile + downloads that release's linux/amd64 binary and checks it against the + checksum the release published, which the command reads from the release + page and pins in the file. It never overwrites a file you edited. + +2. Commit both files and push them. + +3. On Render, choose **New**, then **Blueprint**, pick the repository, and set + the Blueprint path to `.abcd/relay/render.yaml`. Render creates one free web + service that runs `abcd session serve` behind its own proxy. + +4. In the service's **Environment**, set `ABCD_SESSION_GITHUB_CLIENT_ID` and + `ABCD_SESSION_GITHUB_CLIENT_SECRET` from your + [sign-in app](#set-up-github-sign-in), or leave both empty to go without + sign-in. + +The blueprint turns automatic deploys off: a session lives in the relay's +memory, so a redeploy ends every open session. Deploy by hand between calls. +A free Render service also sleeps after a quiet spell and wakes on the next +visit, and a relay that slept holds no session. +To move to a newer abcd release, run `abcd session blueprint` again with that +release; a file you have not edited is replaced. + +## Set up GitHub sign-in + +A team member proves they can change the repository by signing in with GitHub, +through a sign-in app the team registers once. Without one, a team member +pastes a GitHub personal access token instead. Either way the relay asks +GitHub for the person's login and whether they may push to the repository, and +keeps neither the token nor anything else once it has the answer. + +1. On GitHub, register a new OAuth app under the team's organisation or your + account. Set its callback URL to the address the session page is served + from, `https://abcdesign.app/`. +2. Note the app's client id, and generate a client secret. +3. Give both to the relay through its environment: + - `ABCD_SESSION_GITHUB_CLIENT_ID`: the client id, which is public. + - `ABCD_SESSION_GITHUB_CLIENT_SECRET`: the secret. The relay reads it by + this name, adds it only to its own exchange with GitHub, and never logs + it or sends it to a browser. + +Keeping the relay and its secret safe is the team's responsibility. + +## Who can join + +- Anyone the host gives the join code to joins as a guest. A guest sees the + brief and everyone's changes, and proposes changes for a team member to + approve. +- A participant who signs in, and whom GitHub reports as able to push to the + repository, joins as a team member and edits directly. + +Twenty wrong join codes from one address within ten minutes stop that address +from joining until the ten minutes pass. From 862ace7f82b8a72b29387e3ca68ad703eb508970 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:36:12 +0100 Subject: [PATCH 014/165] feat: switch on a project site on GitHub Pages at setup `abcd site setup --provider github-pages` asks whether to switch the project site on, naming its address, before it writes anything. A yes records "publish": true in the composition's hosting block, writes the site's inputs and adds the release workflow, and nothing else; a no writes nothing. The provider publishes public repositories only, so setup reads the repository's visibility from the forge first and refuses a private one in one line. The workflow template takes the provider's profile: the github-pages deploy job runs in the github-pages environment with the run's own token and three permissions, checks out nothing, and the render job reads the switch at the released commit, building and publishing nothing while it is off, and passes --base-path // to the build and the gate. The cloudflare workflow is byte for byte what it was, pinned by a golden file. With the switch off, setup touches nothing remote and offers to remove the workflow it added, keeping one edited by hand. The forge stage manages site-render and the provider's own environment; a provider with no secrets lists no secret step and resolves no credential. Assisted-by: Claude:claude-opus-5-5 --- .../development/brief/04-surfaces/22-site.md | 1 + .abcd/development/release/surface.json | 7 + commands/site.md | 41 ++- docs/reference/cli/commands.md | 9 +- .../hosting/githubpages/githubpages.go | 14 + .../hosting/githubpages/githubpages_test.go | 2 +- internal/adapter/hosting/hosting.go | 3 + internal/core/site/credential.go | 9 +- internal/core/site/forge.go | 16 + internal/core/site/forge_pages_test.go | 51 +++ internal/core/site/hosting.go | 20 +- internal/core/site/providers.go | 22 +- internal/core/site/setup.go | 263 ++++++++++++-- internal/core/site/setup_pages_test.go | 343 ++++++++++++++++++ internal/core/site/setup_test.go | 33 +- .../core/site/setup_workflow_golden_test.go | 20 + internal/core/site/setupsrc/site.yml.tmpl | 56 ++- .../testdata/site-workflow-cloudflare.yml | 210 +++++++++++ internal/surface/cli/site.go | 6 +- internal/surface/cli/site_setup_test.go | 26 ++ 20 files changed, 1068 insertions(+), 84 deletions(-) create mode 100644 internal/core/site/forge_pages_test.go create mode 100644 internal/core/site/setup_pages_test.go create mode 100644 internal/core/site/setup_workflow_golden_test.go create mode 100644 internal/core/site/testdata/site-workflow-cloudflare.yml diff --git a/.abcd/development/brief/04-surfaces/22-site.md b/.abcd/development/brief/04-surfaces/22-site.md index 3803f938b5..ce26905116 100644 --- a/.abcd/development/brief/04-surfaces/22-site.md +++ b/.abcd/development/brief/04-surfaces/22-site.md @@ -326,6 +326,7 @@ Sub-verbs: none. | `--confirm` | bool | | `--domain` | string | | `--name` | string | +| `--provider` | string | | `--yes` | bool | diff --git a/.abcd/development/release/surface.json b/.abcd/development/release/surface.json index f877768e88..3d2c67f6cd 100644 --- a/.abcd/development/release/surface.json +++ b/.abcd/development/release/surface.json @@ -3404,6 +3404,13 @@ "required": false, "hidden": false }, + { + "name": "provider", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + }, { "name": "yes", "shorthand": "", diff --git a/commands/site.md b/commands/site.md index 127d78ff78..31f61aa97b 100644 --- a/commands/site.md +++ b/commands/site.md @@ -122,8 +122,8 @@ sets up the site of a repository abcd manages, in three stages, and emits `{ "status": …, "files": […], "environments": […], "host": {…}, "remaining": […], "notes": […] }` (with `added_labels` and `labels_file` when a label was added): -- `files` — the repository half, each `written`, `current`, `kept` or - `refused`: `.abcd/site.json` (derived from the identity block and +- `files` — the repository half, each `written`, `current`, `kept`, `removed` + (a switched-off project site's workflow, below) or `refused`: `.abcd/site.json` (derived from the identity block and `docs/README.md`), the static inputs under `site-src/`, the workflow `.github/workflows/site.yml` (render on each published release with abcd's verified binary, deploy from the rendered archive) and `wrangler.jsonc`. The @@ -164,6 +164,43 @@ composition's `hosting` block. The composition's `pages` block switches pages of the closed set (`landing`, `explorer`, `record_pages`, `graph`, `timeline`, `glossary`, `status`) off; it cannot add one. +### A project's own GitHub Pages + +```bash +"${CLAUDE_PLUGIN_ROOT}/abcd" site setup --provider github-pages --json +``` + +publishes the project's about page and development record on its own GitHub +Pages at each release, at `https://.github.io//` (or at the root of +a custom domain given with `--domain`). It is the same verb with a second +provider, and these things differ: + +- **The switch.** Before anything is written, setup asks whether to switch the + project site on, naming the address. A yes records `"publish": true` in the + composition's `hosting` block, writes the site's inputs under `site-src/` and + adds `.github/workflows/site.yml`; nothing else in the repository changes. A + no, or an unanswered run, writes nothing and changes nothing remote, and the + status is `declined`. +- **Public repositories only.** Setup reads whether the repository is public + from the forge first, and refuses a private one (or one whose visibility it + cannot read) in one line, before it asks or writes anything. +- **No secret and no credential.** The deploy job runs in the `github-pages` + environment with the run's own short-lived token (`contents: read`, + `pages: write`, `id-token: write`) and nothing else; there is no host + configuration file and no `gh secret set` step. The host stage creates the + Pages site (built by the workflow), sets the custom domain only when one was + given, and reports the address, acting through `gh` as you, the way the forge + stage does. Environments are `site-render` and `github-pages`. +- **The base path.** The workflow builds and gates the site with + `--base-path //`, the path GitHub serves it below (the root for a custom + domain or an `.github.io` repository). +- **Switching it off.** Set `"publish"` to `false` in the composition's + `hosting` block. The workflow reads the switch at each released commit, so the + next release publishes nothing. A later `abcd site setup` touches nothing + remote and offers to remove the workflow it added, `removed` on a yes; a + workflow edited by hand is `kept`. Pages already published stay up until they + are unpublished in the repository's settings. + Report each stage's outcome, then the remaining steps in order. Never paste a credential into the conversation: the user stores it themselves with `abcd ahoy credential hosting.cloudflare` (see `/abcd:ahoy`), which explains where it can diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md index e75fe38cd4..7bc6ac7eea 100644 --- a/docs/reference/cli/commands.md +++ b/docs/reference/cli/commands.md @@ -3751,10 +3751,11 @@ Take the website from this checkout to a live address: Writes its files, and the **Flags:** ``` - --confirm replace a workflow or host configuration that differs from what setup writes - --domain string custom domain to route to the host when the composition names none - --name string host name when the composition names none (default: the repository's name) - --yes confirm the forge and host changes without being asked (never the install of a missing gh); without it an unanswered run declines them + --confirm replace a workflow or host configuration that differs from what setup writes + --domain string custom domain to route to the host when the composition names none + --name string host name when the composition names none (default: the repository's name) + --provider string hosting provider when the composition names none: cloudflare or github-pages (default cloudflare); github-pages asks whether to switch the project site on + --yes confirm the forge and host changes without being asked (never the install of a missing gh); without it an unanswered run declines them ``` ### `abcd spec` diff --git a/internal/adapter/hosting/githubpages/githubpages.go b/internal/adapter/hosting/githubpages/githubpages.go index 453b3f53c8..0a31da0b31 100644 --- a/internal/adapter/hosting/githubpages/githubpages.go +++ b/internal/adapter/hosting/githubpages/githubpages.go @@ -97,9 +97,23 @@ func (Adapter) Profile(s hosting.Site) hosting.Profile { Switched: true, PublicOnly: true, BasePath: basePath(s), + Address: address(s), } } +// address is where GitHub will serve the site: the custom domain, or the +// owner's github.io host below the base path. +func address(s hosting.Site) string { + owner, _, ok := splitRepo(s.Repo) + switch { + case s.Domain != "": + return "https://" + s.Domain + "/" + case !ok: + return "" + } + return "https://" + strings.ToLower(owner) + ".github.io" + basePath(s) +} + // basePath is where GitHub serves the site: the root of a custom domain, the // root of an owner's own owner.github.io repository, and // for any other // repository. diff --git a/internal/adapter/hosting/githubpages/githubpages_test.go b/internal/adapter/hosting/githubpages/githubpages_test.go index dedc57975b..96e614ed90 100644 --- a/internal/adapter/hosting/githubpages/githubpages_test.go +++ b/internal/adapter/hosting/githubpages/githubpages_test.go @@ -53,7 +53,7 @@ func TestTheProfileIsTheRunsOwnTokenOnly(t *testing.T) { want := hosting.Profile{ Environment: "github-pages", Permissions: []string{"contents: read", "pages: write", "id-token: write"}, - Switched: true, PublicOnly: true, BasePath: "/widgets/", + Switched: true, PublicOnly: true, BasePath: "/widgets/", Address: "https://acme.github.io/widgets/", } if !reflect.DeepEqual(p, want) { t.Fatalf("profile = %+v, want %+v", p, want) diff --git a/internal/adapter/hosting/hosting.go b/internal/adapter/hosting/hosting.go index c858284607..427ea80b69 100644 --- a/internal/adapter/hosting/hosting.go +++ b/internal/adapter/hosting/hosting.go @@ -58,6 +58,9 @@ type Profile struct { // BasePath is the path the site is served below on the host, with a // leading and a trailing slash; "/" is the root. BasePath string + // Address is where the site will be served, when the provider can say so + // before the host exists; empty when it cannot. + Address string } // State is what the host holds for a site, read without writing anything. diff --git a/internal/core/site/credential.go b/internal/core/site/credential.go index e853f8a2cd..d2c20fcc5d 100644 --- a/internal/core/site/credential.go +++ b/internal/core/site/credential.go @@ -17,7 +17,7 @@ import ( // CredentialService is the walkthrough's service for name when adapter reads // it: false for a name the site setup does not read. func CredentialService(name string, adapter hosting.Adapter) (credential.Service, bool) { - if adapter == nil || name != adapter.CredentialName() { + if adapter == nil || adapter.CredentialName() == "" || name != adapter.CredentialName() { return credential.Service{}, false } return credential.Service{ @@ -48,11 +48,14 @@ func CredentialServiceFor(name string) (credential.Service, bool) { } // CredentialNames are the credentials the site setup reads, one per hosting -// provider. +// provider that reads one. func CredentialNames() []string { out := make([]string, 0, len(adapters)) for _, a := range adapters { - out = append(out, a.CredentialName()) + // A provider that acts through the forge reads no credential. + if a.CredentialName() != "" { + out = append(out, a.CredentialName()) + } } return out } diff --git a/internal/core/site/forge.go b/internal/core/site/forge.go index decea03596..c61b925e0d 100644 --- a/internal/core/site/forge.go +++ b/internal/core/site/forge.go @@ -22,6 +22,8 @@ type Forge interface { // DefaultBranch reads the repository's default branch: the branch the // workflow gates on and the environments admit. DefaultBranch(ctx context.Context) (string, error) + // Private reads whether the repository is private. + Private(ctx context.Context) (bool, error) // Environments reads every environment the repository has, by name. Environments(ctx context.Context) (map[string]EnvironmentState, error) // Policies reads one environment's deployment branch and tag policies. @@ -109,6 +111,20 @@ func (g *ghForge) DefaultBranch(context.Context) (string, error) { return doc.DefaultBranch, nil } +func (g *ghForge) Private(context.Context) (bool, error) { + out, err := g.api(nil, "", "repos/"+g.repo) + if err != nil { + return false, err + } + var doc struct { + Private *bool `json:"private"` + } + if err := json.Unmarshal(out, &doc); err != nil || doc.Private == nil { + return false, errors.New("the repository response does not say whether it is private") + } + return *doc.Private, nil +} + // perPage is the largest page the forge serves a list read in. const perPage = 100 diff --git a/internal/core/site/forge_pages_test.go b/internal/core/site/forge_pages_test.go new file mode 100644 index 0000000000..498bd135b7 --- /dev/null +++ b/internal/core/site/forge_pages_test.go @@ -0,0 +1,51 @@ +package site + +// The github-pages provider's forge transport and the forge's visibility read +// (itd-2610091010348892), over in-process fakes of `gh api`. + +import ( + "context" + "reflect" + "testing" +) + +func TestTheForgeReadsWhetherTheRepositoryIsPrivate(t *testing.T) { + f := newFakeGHAPI() + for _, want := range []bool{true, false} { + f.objects["repos/"+f.repo] = map[string]any{"default_branch": "main", "private": want} + got, err := f.forge().Private(context.Background()) + if err != nil || got != want { + t.Fatalf("private = %v, %v; want %v", got, err, want) + } + } + f.objects["repos/"+f.repo] = map[string]any{"default_branch": "main"} + if _, err := f.forge().Private(context.Background()); err == nil { + t.Fatal("a repository response that does not say whether it is private was read as public") + } +} + +func TestThePagesProviderSpeaksThroughGhAsThePerson(t *testing.T) { + type call struct { + stdin string + args []string + } + var calls []call + api := ghPagesAPI(func(_ string, stdin []byte, args ...string) ([]byte, error) { + calls = append(calls, call{string(stdin), args}) + return []byte("{}"), nil + }) + if _, err := api(context.Background(), "GET", "repos/acme/widgets/pages", nil); err != nil { + t.Fatal(err) + } + if _, err := api(context.Background(), "POST", "repos/acme/widgets/pages", []byte(`{"build_type":"workflow"}`)); err != nil { + t.Fatal(err) + } + head := []string{"api", "--hostname", "github.com", "-H", "Accept: application/vnd.github+json"} + want := []call{ + {"", append(append([]string{}, head...), "repos/acme/widgets/pages")}, + {`{"build_type":"workflow"}`, append(append([]string{}, head...), "--method", "POST", "repos/acme/widgets/pages", "--input", "-")}, + } + if !reflect.DeepEqual(calls, want) { + t.Fatalf("calls = %q\nwant %q", calls, want) + } +} diff --git a/internal/core/site/hosting.go b/internal/core/site/hosting.go index 7259a427ee..7bc814a707 100644 --- a/internal/core/site/hosting.go +++ b/internal/core/site/hosting.go @@ -1,6 +1,10 @@ package site -import "regexp" +import ( + "regexp" + + "github.com/intentdriven/abcd/internal/adapter/hosting" +) // Hosting is the manifest's `hosting` block: which provider adapter `abcd site // setup` routes the site through, the name of the host it creates there, and the @@ -11,8 +15,16 @@ type Hosting struct { Provider string `json:"provider"` Name string `json:"name"` Domain string `json:"domain,omitempty"` + // Publish is the project-site switch of a provider that publishes only + // while it is on (github-pages, itd-2610091010348892): setup asks for it + // and writes `"publish": true`, and the release workflow publishes nothing + // while it is anything else. A provider without a switch refuses it. + Publish *bool `json:"publish,omitempty"` } +// On reports whether the switch is on. +func (h *Hosting) On() bool { return h != nil && h.Publish != nil && *h.Publish } + // hostNameRe is the host-name charset: lowercase letters, digits and inner // hyphens, at most 63 characters. It is the narrowest of the providers' rules // and is applied before the name reaches an API path or a workflow file. @@ -31,9 +43,13 @@ func (h *Hosting) validate(bad func(string, ...any) error) error { if h.Provider == "" { return bad("hosting.provider is empty; it names the adapter setup routes the site through") } - if _, ok := adapterNamed(h.Provider); !ok { + a, ok := adapterNamed(h.Provider) + if !ok { return bad("hosting.provider %q is not a provider abcd ships (%s)", h.Provider, providerList()) } + if h.Publish != nil && !a.Profile(hosting.Site{}).Switched { + return bad("hosting.publish is the switch of a provider that publishes only while it is on; %s deploys every release and reads no switch", h.Provider) + } if !hostNameRe.MatchString(h.Name) { return bad("hosting.name %q is not a host name (lowercase letters, digits and inner hyphens, at most 63)", h.Name) } diff --git a/internal/core/site/providers.go b/internal/core/site/providers.go index 961c1f79c3..13cf2640cc 100644 --- a/internal/core/site/providers.go +++ b/internal/core/site/providers.go @@ -1,16 +1,36 @@ package site import ( + "context" "strings" "github.com/intentdriven/abcd/internal/adapter/hosting" "github.com/intentdriven/abcd/internal/adapter/hosting/cloudflare" + "github.com/intentdriven/abcd/internal/adapter/hosting/githubpages" + "github.com/intentdriven/abcd/internal/core/ahoy" ) // adapters is the provider list: every hosting adapter abcd ships, by manifest // key. A second provider is one implementation of hosting.Adapter and one entry // here; the verb does not change (itd-2609061543533170 criterion 3). -var adapters = []hosting.Adapter{cloudflare.Adapter{}} +var adapters = []hosting.Adapter{cloudflare.Adapter{}, githubpages.Adapter{API: ghPagesAPI(ahoy.GH)}} + +// ghPagesAPI is the github-pages provider's forge: `gh api` as the person who +// invoked the verb, exactly as the forge stage speaks (ghForge.api), so abcd +// holds no GitHub token for it either. +func ghPagesAPI(gh func(dir string, stdin []byte, args ...string) ([]byte, error)) githubpages.API { + return func(_ context.Context, method, path string, body []byte) ([]byte, error) { + args := []string{"api", "--hostname", ahoy.GitHubHost, "-H", "Accept: application/vnd.github+json"} + if method != "" && method != "GET" { + args = append(args, "--method", method) + } + args = append(args, path) + if body != nil { + args = append(args, "--input", "-") + } + return gh("", body, args...) + } +} // DefaultProvider is the provider setup routes through when the manifest names // none. diff --git a/internal/core/site/setup.go b/internal/core/site/setup.go index a579d567bd..fd18ca5204 100644 --- a/internal/core/site/setup.go +++ b/internal/core/site/setup.go @@ -114,6 +114,10 @@ const ( HostNotReached = "not_reached" ) +// FileRemoved is a file outcome: setup removed the release workflow it added, +// because the project site is switched off. +const FileRemoved scaffold.FileStatus = "removed" + // ErrNotManaged refuses a folder abcd does not manage. var ErrNotManaged = errors.New("site setup: this is not a repository abcd manages (run `abcd ahoy install` first)") @@ -129,6 +133,10 @@ type SetupRequest struct { // Name and Domain seed the hosting block when the composition has none. // Name defaults to the repository's own name. Name, Domain string + // Provider seeds the hosting block's provider when the composition has + // none; empty is DefaultProvider. A provider with a switch (github-pages) + // is switched on only by a yes to the question setup asks. + Provider string // Confirm replaces machinery that drifted from what setup writes. Confirm bool // Asker confirms each remote write; nil declines them all. @@ -233,7 +241,31 @@ func Setup(req SetupRequest) (SetupResult, error) { offerNote = strings.Join(offer, "\n") } } + if forge != nil { + s.Repo = forge.Repo() + } else if repo, rerr := ahoy.GitHubRepo(root); rerr == nil { + s.Repo = repo + } + prof := adapter.Profile(s) + switchedOff := prof.Switched && manifestBytes == nil && !hostingBlock.On() + // A public-only provider refuses a private repository before anything is + // asked or written. A switched-off site is exempt: setup only offers to + // take its workflow away. + if prof.PublicOnly && !switchedOff { + if forge == nil { + return SetupResult{}, fmt.Errorf("site setup: %s publishes public repositories only, and whether this one is public could not be read from the forge", adapter.Name()) + } + private, perr := forge.Private(ctx) + if perr != nil { + return SetupResult{}, fmt.Errorf("site setup: %s publishes public repositories only, and whether %s is public could not be read: %s", + adapter.Name(), forge.Repo(), firstLine(perr.Error())) + } + if private { + return SetupResult{}, fmt.Errorf("site setup: %s is private, and %s publishes public repositories only for now", forge.Repo(), adapter.Name()) + } + } branch, branchNote := defaultBranch(ctx, root, forge) + envNames := []string{EnvRender, prof.Environment} res := SetupResult{Host: HostOutcome{Provider: adapter.Name(), Name: s.Name, Domain: s.Domain, Status: HostNotReached}} if offerNote != "" { @@ -242,6 +274,29 @@ func Setup(req SetupRequest) (SetupResult, error) { if branchNote != "" { res.Notes = append(res.Notes, branchNote) } + if switchedOff { + return setupSwitchedOff(root, branch, adapter, s, req, res) + } + // The switch (the PT's 2026-10-14 ruling, "ask at setup"): a new + // composition naming a provider that publishes only while switched on is + // written only on a yes, and then with the switch on. + if prof.Switched && manifestBytes != nil { + if req.Asker == nil || !req.Asker.Confirm(switchQuestion(adapter, prof)) { + res.Status = StatusDeclined + res.Files, res.Environments = []scaffold.FileOutcome{}, []EnvironmentOutcome{} + res.Host.Detail = "not reached: the project site was not switched on" + res.Notes = append(res.Notes, "the project site was not switched on, so nothing was written and nothing remote was changed") + res.Remaining = append(res.Remaining, "re-run `abcd site setup --provider "+adapter.Name()+"` and answer yes, to switch the project site on") + return res, nil + } + on := true + manifest.Hosting.Publish, hostingBlock.Publish = &on, &on + raw, merr := json.MarshalIndent(manifest, "", " ") + if merr != nil { + return SetupResult{}, merr + } + manifestBytes = append(raw, '\n') + } // Stage 1: the repository. planned, err := plannedFiles(root, manifest, manifestBytes, branch, adapter, s) @@ -258,7 +313,7 @@ func Setup(req SetupRequest) (SetupResult, error) { } else { res.Notes = append(res.Notes, "a write failed, so no remote change was attempted: "+scrubRoot(werr, root)) } - for _, env := range []string{EnvRender, EnvDeploy} { + for _, env := range envNames { res.Environments = append(res.Environments, EnvironmentOutcome{Name: env, Status: RemoteNotReached}) } return res, nil @@ -271,7 +326,7 @@ func Setup(req SetupRequest) (SetupResult, error) { res.Status = StatusRefused res.Notes = append(res.Notes, "the missing interface labels could not be added to "+manifest.UIStrings+ ", so no remote change was attempted: "+scrubRoot(aerr, root)) - for _, env := range []string{EnvRender, EnvDeploy} { + for _, env := range envNames { res.Environments = append(res.Environments, EnvironmentOutcome{Name: env, Status: RemoteNotReached}) } return res, nil @@ -298,16 +353,16 @@ func Setup(req SetupRequest) (SetupResult, error) { // sets the token first, and the workflow's first run creates the deploy // environment with no policy at all: deployable from any branch. createStep := fmt.Sprintf("create the forge environments %s and %s, each admitting only branch %s and tags v*, "+ - "as the workflow's header describes, before setting any deploy secret", EnvRender, EnvDeploy, branch) + "as the workflow's header describes, before setting any deploy secret", envNames[0], envNames[1], branch) if forge == nil { envOK = false - for _, env := range []string{EnvRender, EnvDeploy} { + for _, env := range envNames { res.Environments = append(res.Environments, EnvironmentOutcome{Name: env, Status: RemoteUnreachable}) } res.Remaining = append(res.Remaining, createStep) } else { res.Repo = forge.Repo() - envs, st, note, steps := setupEnvironments(ctx, forge, branch, req.Asker) + envs, st, note, steps := setupEnvironments(ctx, forge, branch, envNames, req.Asker) res.Environments = envs res.Remaining = append(res.Remaining, steps...) switch st { @@ -315,7 +370,7 @@ func Setup(req SetupRequest) (SetupResult, error) { changed = true case RemoteDeclined: declined, envOK = true, false - res.Remaining = append(res.Remaining, "re-run `abcd site setup` and confirm, to create the environments "+EnvRender+" and "+EnvDeploy) + res.Remaining = append(res.Remaining, "re-run `abcd site setup` and confirm, to create the environments "+envNames[0]+" and "+envNames[1]) case RemoteRefused: refused, envOK = true, false res.Notes = append(res.Notes, note) @@ -329,7 +384,7 @@ func Setup(req SetupRequest) (SetupResult, error) { if refused { res.Host.Detail = "not reached: the forge stage failed" } else { - hc, hdeclined, hrefused := setupHost(ctx, adapter, s, req) + hc, hdeclined, hrefused := setupHost(ctx, adapter, s, forge != nil, req) res.Host = hc switch { case hrefused: @@ -355,7 +410,7 @@ func Setup(req SetupRequest) (SetupResult, error) { // The deploy environment's secrets: never set by abcd (a value would pass // through it), but their presence is readable by name. - res.Remaining = append(res.Remaining, secretSteps(ctx, forge, envOK, adapter, &res)...) + res.Remaining = append(res.Remaining, secretSteps(ctx, forge, envOK, adapter, prof.Environment, &res)...) if wrote > 0 { var paths []string @@ -436,22 +491,29 @@ func setupComposition(root string, req SetupRequest) (Manifest, []byte, error) { switch { case err == nil: if m.Hosting == nil { - h := &Hosting{Provider: DefaultProvider, Name: req.Name, Domain: req.Domain} + h := &Hosting{Provider: providerOf(req), Name: req.Name, Domain: req.Domain} if h.Name == "" { h.Name = defaultHostName(root) } if err := h.validate(bad); err != nil { return Manifest{}, nil, err } - if req.Domain != "" || req.Name != "" { + if a, ok := adapterNamed(h.Provider); ok && a.Profile(hosting.Site{}).Switched { + on := true + h.Publish = &on + } + if req.Domain != "" || req.Name != "" || req.Provider != "" { // The composition is the repository's own, so setup does not // rewrite it; the domain has to live in it for the next run to // route the same one. blk, _ := json.Marshal(h) - return Manifest{}, nil, fmt.Errorf("site setup: %s exists and declares no hosting block; add `\"hosting\": %s` to it rather than passing --name or --domain", + return Manifest{}, nil, fmt.Errorf("site setup: %s exists and declares no hosting block; add `\"hosting\": %s` to it rather than passing --name, --domain or --provider", ManifestRelPath, blk) } m.Hosting = h + } else if req.Provider != "" && req.Provider != m.Hosting.Provider { + return Manifest{}, nil, fmt.Errorf("site setup: %s already names the provider %q; edit its hosting block to change it", + ManifestRelPath, m.Hosting.Provider) } else if (req.Name != "" && req.Name != m.Hosting.Name) || (req.Domain != "" && req.Domain != m.Hosting.Domain) { return Manifest{}, nil, fmt.Errorf("site setup: %s already names the host %q and domain %q; edit its hosting block to change them", ManifestRelPath, m.Hosting.Name, m.Hosting.Domain) @@ -493,7 +555,7 @@ func setupComposition(root string, req SetupRequest) (Manifest, []byte, error) { Hero: Hero{Page: hero, Figure: figureFirstImage}, Chapters: []Chapter{{Letter: "a", Page: hero, Layout: LayoutProse}}, }, - Hosting: &Hosting{Provider: DefaultProvider, Name: name, Domain: req.Domain}, + Hosting: &Hosting{Provider: providerOf(req), Name: name, Domain: req.Domain}, } if err := m.validate(); err != nil { return Manifest{}, nil, err @@ -541,13 +603,14 @@ func plannedFiles(root string, m Manifest, manifestBytes []byte, branch string, } planned = append(planned, scaffold.PlannedFile{Path: rel, Data: data, Seed: true}) } - wf, err := renderSiteWorkflow(branch, adapter) + wf, err := renderSiteWorkflow(branch, adapter, s) if err != nil { return nil, err } planned = append(planned, scaffold.PlannedFile{Path: SiteWorkflowRelPath, Data: wf}) - rel, data := adapter.HostConfig(s) - planned = append(planned, scaffold.PlannedFile{Path: rel, Data: data}) + if rel, data := adapter.HostConfig(s); rel != "" { + planned = append(planned, scaffold.PlannedFile{Path: rel, Data: data}) + } // The writer resolves each path under the root by name, so a symlinked // directory on the way would carry a write out of the repository. Refuse // the run before any write rather than follow one. @@ -580,9 +643,9 @@ func refuseSymlinkedAncestor(root, rel string) error { return nil } -// renderSiteWorkflow renders the workflow for the repository's default branch -// and the provider's deploy step. -func renderSiteWorkflow(branch string, adapter hosting.Adapter) ([]byte, error) { +// renderSiteWorkflow renders the workflow for the repository's default branch, +// the provider's deploy job and the site it publishes. +func renderSiteWorkflow(branch string, adapter hosting.Adapter, s hosting.Site) ([]byte, error) { raw, err := setupSources.ReadFile("setupsrc/site.yml.tmpl") if err != nil { return nil, err @@ -592,6 +655,20 @@ func renderSiteWorkflow(branch string, adapter hosting.Adapter) ([]byte, error) return nil, err } rel, _ := adapter.HostConfig(hosting.Site{Name: "x"}) + prof := adapter.Profile(s) + ifOn, baseFlag := "", "" + if prof.Switched { + ifOn = " if: steps.switch.outputs.on == 'true'\n" + } + base, err := NormaliseBasePath(prof.BasePath) + if err != nil { + return nil, err + } + if base != "/" { + // Plain path segments only (NormaliseBasePath), so the flag is a + // literal in the shell line and nothing in it is interpreted. + baseFlag = " --base-path " + base + } var buf bytes.Buffer err = tmpl.Execute(&buf, map[string]any{ // The branch is the forge's, held to scaffold.SafeBranchName, or @@ -603,6 +680,14 @@ func renderSiteWorkflow(branch string, adapter hosting.Adapter) ([]byte, error) "DeployStep": adapter.DeployStep(), "HostConfig": rel, "AbcdRepo": abcdReleaseRepo, + // The deploy job's environment and token permissions, the switch and + // the base path, from the provider's profile. + "DeployEnv": prof.Environment, + "DeployPermissions": prof.Permissions, + "Switched": prof.Switched, + "SwitchFile": ManifestRelPath, + "IfOn": ifOn, + "BaseFlag": baseFlag, }) return buf.Bytes(), err } @@ -624,8 +709,7 @@ func desiredPolicies(branch string) []BranchPolicy { // policies it lacks (their own endpoint, which touches nothing else); one that // admits more refs than that, through its protection mode or through a custom // rule beyond the two it needs, is left as it is, and restricting it is a step. -func setupEnvironments(ctx context.Context, forge Forge, branch string, asker Asker) ([]EnvironmentOutcome, string, string, []string) { - names := []string{EnvRender, EnvDeploy} +func setupEnvironments(ctx context.Context, forge Forge, branch string, names []string, asker Asker) ([]EnvironmentOutcome, string, string, []string) { outcomes := make([]EnvironmentOutcome, len(names)) var steps []string fail := func(what string, err error) ([]EnvironmentOutcome, string, string, []string) { @@ -737,27 +821,42 @@ func containsPolicy(have []BranchPolicy, want BranchPolicy) bool { } // setupHost is the host stage. -func setupHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site, req SetupRequest) (out HostOutcome, declined, refused bool) { +func setupHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site, forgeReachable bool, req SetupRequest) (out HostOutcome, declined, refused bool) { out = HostOutcome{Provider: adapter.Name(), Name: s.Name, Domain: s.Domain, Credential: adapter.CredentialName()} - src := req.Credentials - if src == nil { - src = credential.UserStore() - } - token, err := src.Resolve(adapter.CredentialName()) - if errors.Is(err, credential.ErrNotSet) { - out.Status = HostNoCredential - out.Detail = "no " + adapter.CredentialName() + " credential on this machine, so the host was not contacted" - return out, false, false - } - if err != nil { - out.Status = HostRefused - out.Detail = err.Error() - return out, false, true + token := "" + if adapter.CredentialName() == "" { + // The provider acts through the forge as the person, so it needs the + // forge and resolves no credential. + if !forgeReachable || s.Repo == "" { + out.Status = HostNotReached + out.Detail = "not reached: " + adapter.Name() + " acts through the forge, which is not reachable from this checkout" + return out, false, false + } + } else { + src := req.Credentials + if src == nil { + src = credential.UserStore() + } + var err error + token, err = src.Resolve(adapter.CredentialName()) + if errors.Is(err, credential.ErrNotSet) { + out.Status = HostNoCredential + out.Detail = "no " + adapter.CredentialName() + " credential on this machine, so the host was not contacted" + return out, false, false + } + if err != nil { + out.Status = HostRefused + out.Detail = err.Error() + return out, false, true + } } p := adapter.Connect(token) refuse := func(err error) (HostOutcome, bool, bool) { out.Status = HostRefused - out.Detail = strings.ReplaceAll(err.Error(), token, "[credential]") + out.Detail = err.Error() + if token != "" { + out.Detail = strings.ReplaceAll(out.Detail, token, "[credential]") + } return out, false, true } st, err := p.Inspect(ctx, s) @@ -801,14 +900,17 @@ func setupHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site, req // secretSteps names the deploy environment's secrets that are not set, as the // exact commands that set them. gh reads the value from the terminal, so it // never passes through abcd or a shell history. -func secretSteps(ctx context.Context, forge Forge, envOK bool, adapter hosting.Adapter, res *SetupResult) []string { +func secretSteps(ctx context.Context, forge Forge, envOK bool, adapter hosting.Adapter, env string, res *SetupResult) []string { want := adapter.Secrets() + if len(want) == 0 { + return nil + } missing := want repo := res.Repo if forge != nil && envOK { - names, err := forge.SecretNames(ctx, EnvDeploy) + names, err := forge.SecretNames(ctx, env) if err != nil { - res.Notes = append(res.Notes, "the "+EnvDeploy+" environment's secret names could not be read, so every one is listed: "+err.Error()) + res.Notes = append(res.Notes, "the "+env+" environment's secret names could not be read, so every one is listed: "+err.Error()) } else { have := map[string]bool{} for _, n := range names { @@ -825,7 +927,7 @@ func secretSteps(ctx context.Context, forge Forge, envOK bool, adapter hosting.A sort.Strings(missing) var steps []string for _, name := range missing { - cmd := "gh secret set " + name + " --env " + EnvDeploy + cmd := "gh secret set " + name + " --env " + env if repo != "" { cmd += " --repo " + repo } @@ -833,3 +935,84 @@ func secretSteps(ctx context.Context, forge Forge, envOK bool, adapter hosting.A } return steps } + +// providerOf is the provider a new hosting block names. +func providerOf(req SetupRequest) string { + if req.Provider != "" { + return req.Provider + } + return DefaultProvider +} + +// firstLine keeps a forge error to its first line, so a refusal stays one line. +func firstLine(s string) string { + line, _, _ := strings.Cut(s, "\n") + return line +} + +// switchQuestion is the switch, asked once, before anything is written. +func switchQuestion(adapter hosting.Adapter, prof hosting.Profile) string { + where := adapter.Name() + if prof.Address != "" { + where = prof.Address + " (" + adapter.Name() + ")" + } + return "Switch on the project site? At each release this repository's own workflow builds its about page and development record " + + "and publishes them at " + where + ". Yes records the switch in " + ManifestRelPath + ", writes the site's inputs under site-src/ " + + "and adds " + SiteWorkflowRelPath + "; nothing else in the repository changes" +} + +// setupSwitchedOff is setup for a project site whose switch is off: the next +// release publishes nothing whatever happens here, and setup offers to remove +// the release workflow it added. A workflow that differs from the one setup +// writes is a person's, and is kept. Nothing remote is touched. +func setupSwitchedOff(root, branch string, adapter hosting.Adapter, s hosting.Site, req SetupRequest, res SetupResult) (SetupResult, error) { + res.Files, res.Environments = []scaffold.FileOutcome{}, []EnvironmentOutcome{} + res.Host.Detail = "not reached: the project site is switched off" + res.Notes = append(res.Notes, "the project site is switched off in "+ManifestRelPath+" (hosting.publish is not true), so the next release "+ + "publishes nothing; pages already published stay up until they are unpublished in the repository's settings") + res.Status = StatusNoChange + want, err := renderSiteWorkflow(branch, adapter, s) + if err != nil { + return SetupResult{}, err + } + if err := refuseSymlinkedAncestor(root, SiteWorkflowRelPath); err != nil { + return SetupResult{}, err + } + path := filepath.Join(root, filepath.FromSlash(SiteWorkflowRelPath)) + fi, err := os.Lstat(path) + switch { + case os.IsNotExist(err): + return res, nil + case err != nil: + return SetupResult{}, fmt.Errorf("site setup: %s could not be examined", SiteWorkflowRelPath) + case !fi.Mode().IsRegular(): + res.Files = append(res.Files, scaffold.FileOutcome{Path: SiteWorkflowRelPath, Status: scaffold.StatusKept, + Detail: "not a regular file, so setup leaves it"}) + return res, nil + } + have, err := os.ReadFile(path) + if err != nil { + return SetupResult{}, fmt.Errorf("site setup: %s could not be read", SiteWorkflowRelPath) + } + if !bytes.Equal(have, want) { + res.Files = append(res.Files, scaffold.FileOutcome{Path: SiteWorkflowRelPath, Status: scaffold.StatusKept, + Detail: "differs from the workflow setup writes, so it was edited by hand and setup leaves it; remove it yourself if it is no longer wanted"}) + return res, nil + } + if req.Asker == nil || !req.Asker.Confirm("Remove "+SiteWorkflowRelPath+", the release workflow abcd added? The project site is switched off in "+ + ManifestRelPath+", so it has nothing to publish") { + res.Files = append(res.Files, scaffold.FileOutcome{Path: SiteWorkflowRelPath, Status: scaffold.StatusKept, Detail: "its removal was declined"}) + res.Status = StatusDeclined + res.Remaining = append(res.Remaining, "re-run `abcd site setup` and confirm, to remove "+SiteWorkflowRelPath) + return res, nil + } + if err := os.Remove(path); err != nil { + res.Status = StatusRefused + res.Files = append(res.Files, scaffold.FileOutcome{Path: SiteWorkflowRelPath, Status: scaffold.StatusRefused, Detail: "the removal failed"}) + return res, nil + } + res.Files = append(res.Files, scaffold.FileOutcome{Path: SiteWorkflowRelPath, Status: FileRemoved}) + res.Status = StatusChanged + res.Remaining = append(res.Remaining, "commit the removal and push it to "+branch+": `git add "+SiteWorkflowRelPath+"`") + return res, nil +} diff --git a/internal/core/site/setup_pages_test.go b/internal/core/site/setup_pages_test.go new file mode 100644 index 0000000000..05a5e59cd3 --- /dev/null +++ b/internal/core/site/setup_pages_test.go @@ -0,0 +1,343 @@ +package site + +// `abcd site setup --provider github-pages` (itd-2610091010348892): the switch +// asked at setup, the release workflow added and nothing else, public +// repositories only, and a switched-off site that publishes nothing. The forge +// is the in-process fake, the Pages API githubpagestest's. Nothing here reaches +// a network. + +import ( + "context" + "errors" + "os" + "path/filepath" + "reflect" + "regexp" + "sort" + "strings" + "testing" + + "github.com/intentdriven/abcd/internal/adapter/hosting" + "github.com/intentdriven/abcd/internal/adapter/hosting/githubpages" + "github.com/intentdriven/abcd/internal/adapter/hosting/githubpages/githubpagestest" + "github.com/intentdriven/abcd/internal/core/launch/scaffold" +) + +// asked answers every question the same way and records each one. +type asked struct { + yes bool + questions []string +} + +func (a *asked) Confirm(q string) bool { a.questions = append(a.questions, q); return a.yes } + +// noCredential fails the test if anything resolves a credential: the Pages +// provider acts through the forge and reads none. +type noCredential struct{ t *testing.T } + +func (c noCredential) Resolve(name string) (string, error) { + c.t.Errorf("the credential %q was resolved; the github-pages provider reads none", name) + return "", errors.New("no") +} + +type pagesHarness struct { + *harness + pages *githubpagestest.Server + ask *asked +} + +func newPagesHarness(t *testing.T) *pagesHarness { + t.Helper() + h := &pagesHarness{harness: newHarness(t), pages: githubpagestest.New(), ask: &asked{yes: true}} + h.pages.Repos = map[string]*githubpagestest.Repo{"example-owner/example-site": {}} + return h +} + +func (h *pagesHarness) run(t *testing.T, mutate ...func(*SetupRequest)) SetupResult { + t.Helper() + return h.harness.run(t, append([]func(*SetupRequest){func(r *SetupRequest) { + r.Provider = "github-pages" + r.Adapter = githubpages.Adapter{API: h.pages.API} + r.Credentials = noCredential{t} + r.Asker = h.ask + }}, mutate...)...) +} + +// pagesFiles is the whole repository half for the github-pages provider: the +// project's abcd site settings (the composition holding the switch, and the +// site's inputs setup writes for every provider) and the release workflow. +var pagesFiles = []string{ + ManifestRelPath, "site-src/ui.json", "site-src/site.css", "site-src/site.js", "site-src/record.js", + "site-src/headers", SiteWorkflowRelPath, +} + +func changedPaths(t *testing.T, h *harness) []string { + t.Helper() + var changed []string + for _, line := range strings.Split(strings.TrimRight(h.repo.Git("status", "--porcelain", "-uall"), "\n"), "\n") { + if line != "" { + changed = append(changed, strings.TrimSpace(line[2:])) + } + } + sort.Strings(changed) + return changed +} + +func TestSwitchingOnAProjectSiteSetsTheSwitchAndAddsTheWorkflowOnly(t *testing.T) { + h := newPagesHarness(t) + res := h.run(t) + + if len(h.ask.questions) == 0 || !strings.Contains(h.ask.questions[0], "github-pages") || + !strings.Contains(h.ask.questions[0], "https://example-owner.github.io/example-site/") { + t.Fatalf("the first question is not the switch naming the address: %q", h.ask.questions) + } + want := append([]string(nil), pagesFiles...) + sort.Strings(want) + if got := changedPaths(t, h.harness); !reflect.DeepEqual(got, want) { + t.Fatalf("the repository changed in\n %v\nwant exactly\n %v", got, want) + } + m, err := LoadManifest(h.repo.Root()) + if err != nil { + t.Fatal(err) + } + if m.Hosting == nil || m.Hosting.Provider != "github-pages" || !m.Hosting.On() { + t.Fatalf("hosting = %+v, want github-pages switched on", m.Hosting) + } + if !strings.Contains(string(mustRead(t, h.repo.Root(), ManifestRelPath)), `"publish": true`) { + t.Fatal("the composition does not carry the switch") + } + + wf := string(mustRead(t, h.repo.Root(), SiteWorkflowRelPath)) + for _, s := range []string{"environment: github-pages\n", " pages: write\n", " id-token: write\n", + "--base-path /example-site/", "jq -e '.hosting.publish == true' .abcd/site.json"} { + if !strings.Contains(wf, s) { + t.Errorf("the workflow lacks %q", s) + } + } + + for _, env := range []string{EnvRender, githubpages.Environment} { + pol := h.forge.policies[env] + if !reflect.DeepEqual(pol, []BranchPolicy{{Name: "main", Type: "branch"}, {Name: "v*", Type: "tag"}}) { + t.Errorf("environment %s policies = %v", env, pol) + } + } + if _, ok := h.forge.envs[EnvDeploy]; ok { + t.Errorf("the cloudflare deploy environment %q was created for a Pages site", EnvDeploy) + } + if res.Host.Status != HostWritten || res.Host.Address != "https://example-owner.github.io/example-site/" { + t.Fatalf("host = %+v", res.Host) + } + if p := h.pages.Repos["example-owner/example-site"].Pages; p == nil || p.BuildType != "workflow" { + t.Fatalf("the Pages site = %+v, want one built by the workflow", p) + } + remaining := strings.Join(res.Remaining, "\n") + if strings.Contains(remaining, "gh secret set") || !strings.Contains(remaining, "git add") { + t.Errorf("remaining steps:\n%s", remaining) + } + if res.Status != StatusChanged { + t.Fatalf("status = %q", res.Status) + } + + // The site the composition declares builds below the project's path. + out := t.TempDir() + if _, err := Build(Request{RepoRoot: h.repo.Root(), OutDir: out, Stamp: fixtureStamp, BasePath: "/example-site/"}); err != nil { + t.Fatalf("the switched-on composition does not build: %v", err) + } + if res, err := Check(CheckRequest{RepoRoot: h.repo.Root(), OutDir: out, BasePath: "/example-site/"}); err != nil { + t.Fatal(err) + } else { + for _, f := range res.Findings { + if f.Check == CheckBasePath { + t.Errorf("the Pages build links outside its path: %+v", f) + } + } + } +} + +func TestDecliningTheSwitchChangesNothing(t *testing.T) { + h := newPagesHarness(t) + h.ask.yes = false + res := h.run(t) + if res.Status != StatusDeclined { + t.Fatalf("status = %q, want declined", res.Status) + } + if got := changedPaths(t, h.harness); len(got) != 0 { + t.Fatalf("a declined switch changed %v", got) + } + if len(h.forge.writes) != 0 || len(h.pages.CallLog()) != 0 { + t.Fatalf("a declined switch reached the forge: %v %v", h.forge.writes, h.pages.CallLog()) + } + if len(h.ask.questions) != 1 { + t.Fatalf("questions = %q, want the switch alone", h.ask.questions) + } + if !strings.Contains(strings.Join(res.Remaining, "\n"), "--provider github-pages") { + t.Fatalf("the remaining steps do not say how to switch it on: %v", res.Remaining) + } +} + +func TestAPrivateRepositoryIsRefusedBeforeAnything(t *testing.T) { + h := newPagesHarness(t) + h.forge.private = true + _, err := Setup(SetupRequest{ + RepoRoot: h.repo.Root(), Provider: "github-pages", Asker: h.ask, Forge: h.forge, + Adapter: githubpages.Adapter{API: h.pages.API}, Credentials: noCredential{t}, Context: context.Background(), + }) + if err == nil || !strings.Contains(err.Error(), "private") || !strings.Contains(err.Error(), "public repositories only") || + strings.Contains(err.Error(), "\n") { + t.Fatalf("setup = %v, want a one-line refusal of a private repository", err) + } + if len(h.ask.questions) != 0 || len(changedPaths(t, h.harness)) != 0 || len(h.pages.CallLog()) != 0 { + t.Fatalf("a private repository was asked %q, changed %v, called %v", h.ask.questions, changedPaths(t, h.harness), h.pages.CallLog()) + } +} + +func TestAVisibilityTheForgeCannotReadIsRefused(t *testing.T) { + h := newPagesHarness(t) + h.forge.fail["Private"] = errors.New("HTTP 502") + _, err := Setup(SetupRequest{ + RepoRoot: h.repo.Root(), Provider: "github-pages", Asker: h.ask, Forge: h.forge, + Adapter: githubpages.Adapter{API: h.pages.API}, Credentials: noCredential{t}, Context: context.Background(), + }) + if err == nil || !strings.Contains(err.Error(), "public") { + t.Fatalf("setup = %v, want a refusal", err) + } + if len(changedPaths(t, h.harness)) != 0 { + t.Fatal("an unreadable visibility still wrote files") + } +} + +// switchOff commits the switched-on files and turns the switch off. +func switchOff(t *testing.T, h *pagesHarness) { + t.Helper() + h.repo.Git("add", "-A") + h.repo.Git("commit", "-q", "-m", "switch the project site on") + raw := strings.Replace(string(mustRead(t, h.repo.Root(), ManifestRelPath)), `"publish": true`, `"publish": false`, 1) + if err := os.WriteFile(filepath.Join(h.repo.Root(), ManifestRelPath), []byte(raw), 0o644); err != nil { + t.Fatal(err) + } +} + +func TestASwitchedOffSiteRemovesTheWorkflowAndPublishesNothing(t *testing.T) { + h := newPagesHarness(t) + h.run(t) + switchOff(t, h) + forgeWrites, pagesCalls := len(h.forge.writes), len(h.pages.CallLog()) + h.ask.questions = nil + + res := h.run(t) + if _, err := os.Stat(filepath.Join(h.repo.Root(), SiteWorkflowRelPath)); !os.IsNotExist(err) { + t.Fatalf("the workflow is still there (%v)", err) + } + if got := fileStatuses(res)[SiteWorkflowRelPath]; got != string(FileRemoved) { + t.Fatalf("workflow outcome = %q, want removed", got) + } + if len(h.ask.questions) != 1 || !strings.Contains(h.ask.questions[0], SiteWorkflowRelPath) { + t.Fatalf("questions = %q, want the removal alone", h.ask.questions) + } + if len(h.forge.writes) != forgeWrites || len(h.pages.CallLog()) != pagesCalls { + t.Fatalf("a switched-off run reached the forge: %v %v", h.forge.writes[forgeWrites:], h.pages.CallLog()[pagesCalls:]) + } + if !strings.Contains(strings.Join(res.Notes, "\n"), "publishes nothing") { + t.Fatalf("notes do not say the next release publishes nothing: %v", res.Notes) + } + if res.Status != StatusChanged { + t.Fatalf("status = %q", res.Status) + } + // Nothing else in the repository moved. + if got := changedPaths(t, h.harness); !reflect.DeepEqual(got, []string{ManifestRelPath, SiteWorkflowRelPath}) { + t.Fatalf("changed %v, want the switch and the removed workflow", got) + } + + // A third run has nothing left to do. + h.ask.questions = nil + if res = h.run(t); res.Status != StatusNoChange || len(h.ask.questions) != 0 { + t.Fatalf("third run = %q, asked %q", res.Status, h.ask.questions) + } +} + +func TestASwitchedOffSiteKeepsAWorkflowEditedByHand(t *testing.T) { + h := newPagesHarness(t) + h.run(t) + switchOff(t, h) + wf := filepath.Join(h.repo.Root(), SiteWorkflowRelPath) + if err := os.WriteFile(wf, append(mustRead(t, h.repo.Root(), SiteWorkflowRelPath), "# mine\n"...), 0o644); err != nil { + t.Fatal(err) + } + h.ask.questions = nil + res := h.run(t) + if _, err := os.Stat(wf); err != nil { + t.Fatal("a hand-edited workflow was removed") + } + if got := fileStatuses(res)[SiteWorkflowRelPath]; got != string(scaffold.StatusKept) { + t.Fatalf("workflow outcome = %q, want kept", got) + } + if len(h.ask.questions) != 0 { + t.Fatalf("asked %q about a workflow it will not remove", h.ask.questions) + } +} + +func TestDecliningTheRemovalKeepsTheWorkflow(t *testing.T) { + h := newPagesHarness(t) + h.run(t) + switchOff(t, h) + h.ask.yes = false + res := h.run(t) + if _, err := os.Stat(filepath.Join(h.repo.Root(), SiteWorkflowRelPath)); err != nil { + t.Fatal("a declined removal removed the workflow") + } + if res.Status != StatusDeclined { + t.Fatalf("status = %q, want declined", res.Status) + } +} + +func TestThePagesWorkflowHoldsNoSecretAndInterpolatesNothingIntoAShell(t *testing.T) { + wf, err := renderSiteWorkflow("main", githubpages.Adapter{}, hosting.Site{Name: "widgets", Repo: "acme/widgets"}) + if err != nil { + t.Fatal(err) + } + s := string(wf) + if strings.Contains(s, "{{ secrets.") { + t.Error("the Pages workflow reads a secret") + } + if strings.Contains(s, "for the host configuration") { + t.Error("the Pages deploy job checks out the repository, which it reads nothing from") + } + if !regexp.MustCompile(`(?m)^ deploy:\n needs: render\n if: needs.render.outputs.publish == 'true'\n`).MatchString(s) { + t.Error("the deploy job is not gated on the switch") + } + if got := strings.Count(s, " if: steps.switch.outputs.on == 'true'\n"); got != 5 { + t.Errorf("%d render steps are gated on the switch, want the five after it", got) + } + if !strings.Contains(s, " permissions:\n contents: read\n pages: write\n id-token: write\n steps:\n") { + t.Error("the deploy job's permissions are not exactly the Pages three") + } + inRun, runIndent := false, 0 + for i, line := range strings.Split(s, "\n") { + indent := len(line) - len(strings.TrimLeft(line, " ")) + trimmed := strings.TrimSpace(line) + if inRun && trimmed != "" && indent <= runIndent { + inRun = false + } + if strings.HasPrefix(trimmed, "run:") { + if strings.Contains(trimmed, "${{") { + t.Errorf("line %d interpolates into a shell: %s", i+1, trimmed) + } + inRun, runIndent = true, indent + continue + } + if inRun && strings.Contains(line, "${{") { + t.Errorf("line %d interpolates into a shell: %s", i+1, trimmed) + } + } +} + +func TestThePublishSwitchBelongsToASwitchedProvider(t *testing.T) { + on := true + bad := func(format string, args ...any) error { return errors.New(format) } + if err := (&Hosting{Provider: "cloudflare", Name: "x", Publish: &on}).validate(bad); err == nil { + t.Fatal("a cloudflare hosting block accepted the publish switch, which its workflow never reads") + } + if err := (&Hosting{Provider: "github-pages", Name: "x", Publish: &on}).validate(bad); err != nil { + t.Fatalf("github-pages refused its own switch: %v", err) + } +} diff --git a/internal/core/site/setup_test.go b/internal/core/site/setup_test.go index a6cda5579d..1f8c0b1441 100644 --- a/internal/core/site/setup_test.go +++ b/internal/core/site/setup_test.go @@ -42,6 +42,8 @@ type fakeForge struct { protection map[string][]string // defaultBranch is the repository's default branch as the forge names it. defaultBranch string + // private is whether the forge reports the repository private. + private bool } func newFakeForge() *fakeForge { @@ -61,6 +63,13 @@ func (f *fakeForge) DefaultBranch(context.Context) (string, error) { return f.defaultBranch, nil } +func (f *fakeForge) Private(context.Context) (bool, error) { + if err := f.fail["Private"]; err != nil { + return false, err + } + return f.private, nil +} + func (f *fakeForge) Environments(context.Context) (map[string]EnvironmentState, error) { if err := f.fail["Environments"]; err != nil { return nil, err @@ -347,15 +356,17 @@ func mustRead(t *testing.T, root, rel string) []byte { // --- criterion 3: the seam ------------------------------------------------------- -func TestTheProviderListHasOneAdapterBehindTheSeam(t *testing.T) { - if got := Providers(); !reflect.DeepEqual(got, []string{"cloudflare"}) { - t.Fatalf("providers = %v, want exactly [cloudflare]", got) +func TestTheProviderListHasTwoAdaptersBehindTheSeam(t *testing.T) { + if got := Providers(); !reflect.DeepEqual(got, []string{"cloudflare", "github-pages"}) { + t.Fatalf("providers = %v, want exactly [cloudflare github-pages]", got) } - a, ok := adapterNamed("cloudflare") - if !ok { - t.Fatal("the listed provider does not resolve") + for _, name := range Providers() { + a, ok := adapterNamed(name) + if !ok { + t.Fatalf("the listed provider %s does not resolve", name) + } + var _ hosting.Adapter = a } - var _ hosting.Adapter = a } // --- criterion 4: the page set for any managed repository ------------------------ @@ -672,7 +683,7 @@ func TestAnUnsafeNameOrDomainIsRefused(t *testing.T) { // run block is spliced into the script before the shell parses it, which is // the injection shape; every value reaches the shell through env instead. func TestTheWorkflowInterpolatesNothingIntoAShell(t *testing.T) { - wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}) + wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}, hosting.Site{Name: "x"}) if err != nil { t.Fatal(err) } @@ -709,7 +720,7 @@ func TestTheWorkflowPinsFollowAbcdsOwn(t *testing.T) { if err != nil { t.Fatal(err) } - wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}) + wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}, hosting.Site{Name: "x"}) if err != nil { t.Fatal(err) } @@ -755,7 +766,7 @@ func TestTheSeedSourcesAreAbcdsOwn(t *testing.T) { // successful run of this repository's own release workflow, never a pull // request's and never a fork's. func TestAWorkflowRunFromAForkNeverRenders(t *testing.T) { - wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}) + wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}, hosting.Site{Name: "x"}) if err != nil { t.Fatal(err) } @@ -778,7 +789,7 @@ func TestAWorkflowRunFromAForkNeverRenders(t *testing.T) { // workflows, filters no branch in the trigger (a branch filter drops the tag), // and admits the default branch or a v-tag inside the render job's gate. func TestTheWorkflowFiresOnEveryReleasePath(t *testing.T) { - wf, err := renderSiteWorkflow("trunk", cloudflare.Adapter{}) + wf, err := renderSiteWorkflow("trunk", cloudflare.Adapter{}, hosting.Site{Name: "x"}) if err != nil { t.Fatal(err) } diff --git a/internal/core/site/setup_workflow_golden_test.go b/internal/core/site/setup_workflow_golden_test.go new file mode 100644 index 0000000000..1b1c9b2b8a --- /dev/null +++ b/internal/core/site/setup_workflow_golden_test.go @@ -0,0 +1,20 @@ +package site + +import ( + "testing" + + "github.com/intentdriven/abcd/internal/adapter/hosting" + "github.com/intentdriven/abcd/internal/adapter/hosting/cloudflare" +) + +// TestTheCloudflareWorkflowIsUnchanged pins the workflow setup writes for the +// cloudflare provider byte for byte, so a managed repository that set its +// site up before the github-pages provider existed finds its workflow current +// and is not asked to replace it. +func TestTheCloudflareWorkflowIsUnchanged(t *testing.T) { + wf, err := renderSiteWorkflow("main", cloudflare.Adapter{}, hosting.Site{Name: "x"}) + if err != nil { + t.Fatal(err) + } + golden(t, "site-workflow-cloudflare.yml", wf) +} diff --git a/internal/core/site/setupsrc/site.yml.tmpl b/internal/core/site/setupsrc/site.yml.tmpl index 406d3f2543..b1b2bf7ebd 100644 --- a/internal/core/site/setupsrc/site.yml.tmpl +++ b/internal/core/site/setupsrc/site.yml.tmpl @@ -7,9 +7,16 @@ name: site # Renders this repository's site from each published release with abcd's own # released binary — checksum- and attestation-verified before it runs — and # deploys the rendered artefact to <% .Provider %>. The render job holds no -# credential; the deploy job holds the provider's and runs nothing from the +<% if .HostConfig %># credential; the deploy job holds the provider's and runs nothing from the # repository but the host configuration file (<% .HostConfig %>). -# +<% else %># credential, and neither does the deploy job: it deploys with the run's own +# short-lived token and runs nothing from the repository. +<% end %><% if .Switched %># +# THE SWITCH: the site publishes only while `"publish": true` stands in the +# hosting block of <% .SwitchFile %> at the released commit. Switched off, a +# release builds and publishes nothing, and `abcd site setup` offers to +# remove this file. +<% end %># # Three entry points, one path: # # release: published a release published by a person or a personal token. @@ -34,7 +41,7 @@ name: site # # site-render the render job. No secrets. Deployment policy: branch # `<% .DefaultBranch %>` and tags `v*`. -# site the deploy job. Same policy. Secrets:<% range .Secrets %> <% . %><% end %>. +# <% printf "%-13s" .DeployEnv %> the deploy job. Same policy. <% if .Secrets %>Secrets:<% range .Secrets %> <% . %><% end %>.<% else %>No secrets: the run's own token.<% end %> # # The policy refuses unreviewed workflow content: a dispatch runs this file as # it exists on the ref it was dispatched from, so a branch that is neither the @@ -86,7 +93,8 @@ jobs: contents: read outputs: tag: ${{ steps.release.outputs.tag }} - steps: +<% if .Switched %> publish: ${{ steps.switch.outputs.on }} +<% end %> steps: - name: Resolve the release to render id: release env: @@ -118,8 +126,19 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Download abcd and its checksums - env: +<% if .Switched %> - name: Read the project-site switch at the released commit + id: switch + run: | + set -euo pipefail + if jq -e '.hosting.publish == true' <% .SwitchFile %> >/dev/null 2>&1; then + echo "on=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::the project site is switched off in <% .SwitchFile %>, so this release publishes nothing" + echo "on=false" >> "$GITHUB_OUTPUT" + fi + +<% end %> - name: Download abcd and its checksums +<% .IfOn %> env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail @@ -127,7 +146,7 @@ jobs: --pattern 'abcd-linux-amd64' --pattern 'checksums.txt' --dir "$RUNNER_TEMP" - name: Verify abcd against its release manifest and its provenance - env: +<% .IfOn %> env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail @@ -138,18 +157,18 @@ jobs: install -m 0755 abcd-linux-amd64 "${RUNNER_TEMP}/abcd" - name: Render and check the site - env: +<% .IfOn %> env: TAG: ${{ steps.release.outputs.tag }} run: | set -euo pipefail - "${RUNNER_TEMP}/abcd" site build --out site --version "${TAG#v}" --commit "$(git rev-parse --verify HEAD)" - "${RUNNER_TEMP}/abcd" lint site --out site + "${RUNNER_TEMP}/abcd" site build --out site --version "${TAG#v}" --commit "$(git rev-parse --verify HEAD)"<% .BaseFlag %> + "${RUNNER_TEMP}/abcd" lint site --out site<% .BaseFlag %> - name: Pack the rendered site - run: tar -czf site.tar.gz -C site . +<% .IfOn %> run: tar -czf site.tar.gz -C site . - name: Hand the rendered site to the deploy job - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +<% .IfOn %> uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: site-${{ steps.release.outputs.tag }} path: site.tar.gz @@ -158,19 +177,20 @@ jobs: deploy: needs: render - runs-on: ubuntu-latest +<% if .Switched %> if: needs.render.outputs.publish == 'true' +<% end %> runs-on: ubuntu-latest timeout-minutes: 15 - environment: site + environment: <% .DeployEnv %> permissions: - contents: read - steps: - - name: Check out the released commit for the host configuration +<% range .DeployPermissions %> <% . %> +<% end %> steps: +<% if .HostConfig %> - name: Check out the released commit for the host configuration uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: refs/tags/${{ needs.render.outputs.tag }} persist-credentials: false - - name: Collect the rendered site +<% end %> - name: Collect the rendered site uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: site-${{ needs.render.outputs.tag }} diff --git a/internal/core/site/testdata/site-workflow-cloudflare.yml b/internal/core/site/testdata/site-workflow-cloudflare.yml new file mode 100644 index 0000000000..732a4e4cba --- /dev/null +++ b/internal/core/site/testdata/site-workflow-cloudflare.yml @@ -0,0 +1,210 @@ +name: site + +# Written by `abcd site setup` (itd-2609061543533170). Re-running the verb +# rewrites nothing while this file is unchanged, and refuses a hand edit unless +# it is told to replace it. +# +# Renders this repository's site from each published release with abcd's own +# released binary — checksum- and attestation-verified before it runs — and +# deploys the rendered artefact to cloudflare. The render job holds no +# credential; the deploy job holds the provider's and runs nothing from the +# repository but the host configuration file (wrangler.jsonc). +# +# Three entry points, one path: +# +# release: published a release published by a person or a personal token. +# workflow_run the `release` or `auto-release` workflow finishing on +# main or on a v-tag. A release created with +# the workflow's own GITHUB_TOKEN fires no `release` +# event, so a release workflow's completion is what +# carries those releases here: `auto-release` runs on +# every push to main and calls `release` inside +# its own run, and a hand-pushed tag runs `release` with +# the tag as its head branch. A completion that +# released nothing redeploys the latest published +# release: the same bytes the site already serves, +# unless a dispatch rolled it back to an older tag, +# which the next such completion replaces with the +# latest release again. +# workflow_dispatch a redeploy by hand; an empty tag means the latest +# published release. +# +# THE ENVIRONMENT CONTRACT (the tree's record of the forge's state; `abcd site +# setup` creates both through the forge's API when it is asked to): +# +# site-render the render job. No secrets. Deployment policy: branch +# `main` and tags `v*`. +# site the deploy job. Same policy. Secrets: CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID. +# +# The policy refuses unreviewed workflow content: a dispatch runs this file as +# it exists on the ref it was dispatched from, so a branch that is neither the +# default branch nor a release tag is refused before either job starts. +on: + release: + types: [published] + workflow_run: + workflows: [release, auto-release] + types: [completed] + workflow_dispatch: + inputs: + tag: + description: Release tag to render and deploy (vX.Y.Z). Empty means the latest published release. + required: false + type: string + default: '' + +# One production target, so one queue. A deploy cancelled between uploading +# assets and switching the route leaves the site half-moved. +concurrency: + group: site-production + cancel-in-progress: false + +permissions: + contents: read + +jobs: + render: + # A workflow_run fires for the release workflows' runs whatever triggered + # them, and a head branch is a name a fork can choose. Only a successful + # run of this repository's own release workflows, not of a pull request, + # on the default branch or a v-tag (the trigger filters no branch, because + # a tag-push run's head branch is the tag), reaches the render. Nothing + # from the triggering run is used either way: the tag is resolved from the + # repository's published releases. + if: >- + ${{ github.event_name != 'workflow_run' || ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event != 'pull_request' && + github.event.workflow_run.event != 'pull_request_target' && + github.event.workflow_run.head_repository.full_name == github.repository && + (github.event.workflow_run.head_branch == 'main' || + startsWith(github.event.workflow_run.head_branch, 'v'))) }} + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: site-render + permissions: + contents: read + outputs: + tag: ${{ steps.release.outputs.tag }} + steps: + - name: Resolve the release to render + id: release + env: + GH_TOKEN: ${{ github.token }} + EVENT_TAG: ${{ github.event.release.tag_name }} + INPUT_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag="${EVENT_TAG:-${INPUT_TAG:-}}" + if [ -z "$tag" ]; then + tag="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)" + fi + if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'; then + echo "::error::the release tag is not vX.Y.Z; refusing to render it" + exit 1 + fi + state="$(gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json isPrerelease,isDraft \ + --jq 'if (.isPrerelease or .isDraft) then "true" else "false" end')" + if [ "$state" = true ]; then + echo "::error::$tag is a pre-release or a draft; the site renders published releases only" + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Check out the released commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/tags/${{ steps.release.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - name: Download abcd and its checksums + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release download --repo intentdriven/abcd \ + --pattern 'abcd-linux-amd64' --pattern 'checksums.txt' --dir "$RUNNER_TEMP" + + - name: Verify abcd against its release manifest and its provenance + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + cd "$RUNNER_TEMP" + grep ' abcd-linux-amd64$' checksums.txt | sha256sum -c - + gh attestation verify abcd-linux-amd64 --repo intentdriven/abcd \ + --signer-workflow intentdriven/abcd/.github/workflows/release.yml + install -m 0755 abcd-linux-amd64 "${RUNNER_TEMP}/abcd" + + - name: Render and check the site + env: + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + "${RUNNER_TEMP}/abcd" site build --out site --version "${TAG#v}" --commit "$(git rev-parse --verify HEAD)" + "${RUNNER_TEMP}/abcd" lint site --out site + + - name: Pack the rendered site + run: tar -czf site.tar.gz -C site . + + - name: Hand the rendered site to the deploy job + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: site-${{ steps.release.outputs.tag }} + path: site.tar.gz + if-no-files-found: error + retention-days: 1 + + deploy: + needs: render + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: site + permissions: + contents: read + steps: + - name: Check out the released commit for the host configuration + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/tags/${{ needs.render.outputs.tag }} + persist-credentials: false + + - name: Collect the rendered site + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: site-${{ needs.render.outputs.tag }} + path: ${{ runner.temp }}/site-artifact + + # The archive is audited before a byte of it is unpacked: only relative + # paths, no parent traversal, only regular files and directories. + - name: Audit and unpack the rendered site + run: | + set -euo pipefail + archive="${RUNNER_TEMP}/site-artifact/site.tar.gz" + while IFS= read -r member; do + [ -n "$member" ] || continue + case "$member" in + /*|..|../*|*/../*|*/..) echo "::error::refusing site.tar.gz: unsafe member '$member'"; exit 1 ;; + esac + done <<< "$(tar -tzf "$archive")" + while IFS= read -r t; do + case "$t" in + -|d|'') ;; + *) echo "::error::refusing site.tar.gz: non-regular member (type '$t')"; exit 1 ;; + esac + done <<< "$(tar -tvzf "$archive" | awk '{print substr($1,1,1)}' | sort -u)" + staging="${RUNNER_TEMP}/site-unpacked" + rm -rf "$staging" site + mkdir -p "$staging" site + tar -xzf "$archive" -C "$staging" + cp -R "$staging/." site/ + test -n "$(ls -A site)" + + - name: Deploy to Cloudflare + uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy + wranglerVersion: '4.123.0' diff --git a/internal/surface/cli/site.go b/internal/surface/cli/site.go index 61aafd1c3c..45797cf15a 100644 --- a/internal/surface/cli/site.go +++ b/internal/surface/cli/site.go @@ -283,7 +283,7 @@ func publishedWord(on bool) string { // machine, the host. An unanswered run declines both remote writes, so a script // that pipes nothing changes nothing remote; --yes says yes in advance. func newSiteSetupCommand(asJSON *bool) *cobra.Command { - var name, domain string + var name, domain, provider string var confirm, yes bool cmd := &cobra.Command{ Use: "setup", @@ -305,7 +305,7 @@ func newSiteSetupCommand(asJSON *bool) *cobra.Command { asker = alwaysConfirm{} } res, err := site.Setup(site.SetupRequest{ - RepoRoot: cwd, Name: name, Domain: domain, Confirm: confirm, Asker: asker, + RepoRoot: cwd, Name: name, Domain: domain, Provider: provider, Confirm: confirm, Asker: asker, ConfirmTool: confirmTool, Context: cmd.Context(), }) if err != nil { @@ -328,6 +328,8 @@ func newSiteSetupCommand(asJSON *bool) *cobra.Command { } cmd.Flags().StringVar(&name, "name", "", "host name when the composition names none (default: the repository's name)") cmd.Flags().StringVar(&domain, "domain", "", "custom domain to route to the host when the composition names none") + cmd.Flags().StringVar(&provider, "provider", "", "hosting provider when the composition names none: "+strings.Join(site.Providers(), " or ")+ + " (default "+site.DefaultProvider+"); github-pages asks whether to switch the project site on") cmd.Flags().BoolVar(&confirm, "confirm", false, "replace a workflow or host configuration that differs from what setup writes") cmd.Flags().BoolVar(&yes, "yes", false, "confirm the forge and host changes without being asked (never the install of a missing gh); without it an unanswered run declines them") return cmd diff --git a/internal/surface/cli/site_setup_test.go b/internal/surface/cli/site_setup_test.go index a6dad0727c..74a40fa76f 100644 --- a/internal/surface/cli/site_setup_test.go +++ b/internal/surface/cli/site_setup_test.go @@ -313,3 +313,29 @@ func TestSiteVerbsSayWhichLabelsTheyAdded(t *testing.T) { t.Fatalf("a failed build names the label it added before the error: stderr = %q, err = %v", errOut, err) } } + +// TestSiteSetupTakesTheGitHubPagesProvider: --provider reaches the verb. With +// no forge to say whether the repository is public, the github-pages provider +// refuses before it asks or writes anything, since it publishes public +// repositories only (itd-2610091010348892). +func TestSiteSetupTakesTheGitHubPagesProvider(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + r := gittest.NewRepo(t) + r.Write("AGENTS.md", "# Example\n\n\nmanaged\n\n") + r.Write(".abcd/positioning.json", `{"schema_version": 1, "block": {"file": ".abcd/development/IDENTITY.md", "heading": "Identity (canonical)"}, "severity": "warn", "surfaces": []}`+"\n") + r.Write(".abcd/development/IDENTITY.md", "# Identity\n\n## Identity (canonical)\n\n- **Title:** Example\n- **Tagline:** An example.\n") + r.Write("docs/README.md", "# Example\n\nThe example's documentation.\n") + r.Commit("the example") + t.Chdir(r.Root()) + + out, err := runCLIErr(t, "site", "setup", "--provider", "github-pages", "--yes") + if code := exitCodeOf(err); code != 2 || !strings.Contains(err.Error(), "github-pages publishes public repositories only") { + t.Fatalf("exit %d, %v:\n%s", code, err, out) + } + if _, serr := os.Stat(filepath.Join(r.Root(), ".abcd", "site.json")); !os.IsNotExist(serr) { + t.Fatal("a refused run wrote the composition") + } + if out, err = runCLIErr(t, "site", "setup", "--provider", "no-such-host"); exitCodeOf(err) != 2 || !strings.Contains(err.Error(), "not a provider abcd ships") { + t.Fatalf("an unknown provider: %v\n%s", err, out) + } +} From 6d02379a6a01527d4011c31ff5f3c4960dc73910 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:38:21 +0100 Subject: [PATCH 015/165] docs: record what the github-pages project site delivers Fills spc-2610141709461788's summary, footprint and four landed steps for itd-2610091010348892, and captures iss-2610142036270003: on the github-pages provider the security headers site-src/headers declares are not served, because _headers is Cloudflare's and GitHub Pages ignores it. Refs: iss-2610142036270003 Assisted-by: Claude:claude-opus-5-5 --- ...8-anyone-who-opens-abcdev-app-finds-the.md | 89 ++++++++++++++++++- ...n-the-github-pages-hosting-provider-the.md | 15 ++++ 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 .abcd/work/issues/open/iss-2610142036270003-on-the-github-pages-hosting-provider-the.md diff --git a/.abcd/development/specs/open/spc-2610141709461788-anyone-who-opens-abcdev-app-finds-the.md b/.abcd/development/specs/open/spc-2610141709461788-anyone-who-opens-abcdev-app-finds-the.md index f51651067a..24633147d0 100644 --- a/.abcd/development/specs/open/spc-2610141709461788-anyone-who-opens-abcdev-app-finds-the.md +++ b/.abcd/development/specs/open/spc-2610141709461788-anyone-who-opens-abcdev-app-finds-the.md @@ -9,13 +9,94 @@ production_mode: hand-written ## Summary -_Draft: describe what spc-2610141709461788 delivers for itd-2610091010348892 — scope, approach, and how it satisfies the intent's Acceptance Criteria. This spec is the design record the fidelity review audits against._ +spc-2610141709461788 delivers itd-2610091010348892: a project that switches it +on at setup has its about page and development record built by its own release +workflow at each release and published on its own GitHub Pages, at +`https://.github.io//` (or the root of a custom domain), with no +secret and nothing run by abcd. It is the existing `abcd site setup` pipeline +with a second hosting provider, not a parallel one: one canonical primitive. + +- **The provider.** `internal/adapter/hosting/githubpages` implements the + hosting seam. The repository half holds no secret: the deploy job runs in the + `github-pages` environment with the run's own short-lived token (`contents: + read`, `pages: write`, `id-token: write`) and deploys through + `actions/upload-pages-artifact` v5.0.0 and `actions/deploy-pages` v5.0.1, + pinned to full commits; there is no host configuration file. The host half + speaks the GitHub REST API through `gh` as the person who invoked setup, the + way the forge stage does, so abcd resolves and stores no credential for it: + it reads the repository's visibility and Pages state, creates a Pages site + built by the workflow (`build_type: workflow`), sets the custom domain only + when one was given, and reports the `html_url`. A private repository, a site + published from a branch and a domain routed elsewhere are refused, never + moved. The seam gained `Site.Repo` and one method, `Profile` (deploy + environment and permissions, the switch, the public-only rule, the base path + and the expected address); cloudflare's profile reproduces its workflow byte + for byte. Nothing in the adapter names abcd's own repository, so moving it + later is a composition edit. +- **The switch (criterion 1).** `abcd site setup --provider github-pages` asks + whether to switch the project site on, naming the address, before anything + is written. A yes records `"publish": true` in the `hosting` block of + `.abcd/site.json` and adds `.github/workflows/site.yml`. Setup also writes + the site's inputs under `site-src/`, as it does for every provider; those + count as the site's abcd settings, so nothing outside the site's settings and + the workflow changes (the coordinator's ruling of 2026-10-14). A no writes + nothing. `hosting.publish` is refused on a provider without a switch. +- **Public repositories only (scope condition 1).** Setup reads the + repository's visibility from the forge first and refuses a private one, or + one whose visibility it cannot read, in one line before it asks or writes. +- **Each release (criterion 2).** The workflow is the existing + render-on-release-then-deploy workflow, with the provider's profile: the + render job builds and gates the site with `--base-path //`, and the + deploy job hands it to Pages. +- **The base path.** `abcd site build` and `abcd lint site` take + `--base-path` (default `/`, normalised to a leading and trailing slash, plain + path segments only). Every root-absolute URL the build writes goes through + one helper, `sitePath`; the gate resolves the stylesheet against the prefixed + root and a new `base-path` check refuses a root-absolute link outside it. The + default output is byte-identical (golden files unchanged; this repository's + own 3082-file site identical before and after). +- **The managed-repository line (criterion 3).** Every page opens with "This is + the record of an abcd-managed repository", linking to the repository. The + words are a new `managed_record` interface string in the closed `ui.json` + allowlist, so an older `ui.json` gains it with its default words and the + provenance gate still fails a build on any word no repository file accounts + for (criterion 5). A repository with no package manifest takes its address + from the origin remote on github.com. +- **No secret (criterion 6).** The workflow reads no `secrets.` value and + interpolates no expression into a shell. +- **Switching off (criterion 7).** With `"publish"` anything but `true` at the + released commit, the render job's switch step stops the build and the deploy + job does not run, so the release publishes nothing. `abcd site setup` then + touches nothing remote and offers to remove the workflow it added, keeping + one edited by hand. +- **abcd's own repository (criterion 4)** is not moved in this change (the + product thinker's "at some point"); the route it would take carries no abcd + special case. + +Found and captured, not fixed here: the `_headers` file the build copies is +Cloudflare's, so a project site on GitHub Pages is served without those +security headers (iss-2610142036270003). ## Footprint -- packages: -- tests: +- packages: internal/adapter/hosting, internal/adapter/hosting/cloudflare, internal/adapter/hosting/githubpages, internal/adapter/hosting/githubpages/githubpagestest, internal/core/site, internal/surface/cli +- tests: the base path (normalising, every root-absolute URL prefixed, default output unchanged, the gate against the prefixed root, both verbs' flag); the managed-repository line on every page and its origin fallback; the github-pages adapter over its in-process fake (profile, create, route, address, refusals, every forge failure); setup switched on, declined, private, visibility unreadable, switched off with the workflow removed, kept when hand-edited, kept when the removal is declined; the Pages workflow holding no secret and gated on the switch; the cloudflare workflow pinned byte for byte by a golden file; the forge's visibility read and the `gh api` transport ## Steps -_No steps listed: the spec is built as one step. To split it, list the steps in order as `1. `, each with `- packages:` and `- tests:` indented beneath it; `- landed: <pull request or commit>` marks a step that has landed._ +1. Build and gate a site served below a base path + - packages: internal/core/site, internal/surface/cli + - tests: basepath_test.go, site_basepath_test.go, the unchanged golden files + - landed: ff9eee7b6 +2. Open every page with the managed-repository line + - packages: internal/core/site + - tests: managedline_test.go, the updated golden files + - landed: ffa77caf5 +3. The github-pages hosting adapter and the seam's profile + - packages: internal/adapter/hosting, internal/adapter/hosting/cloudflare, internal/adapter/hosting/githubpages + - tests: githubpages_test.go, cloudflare_test.go + - landed: 52a12eb24 +4. The switch at setup, public repositories only, the provider-aware workflow and switching off + - packages: internal/core/site, internal/surface/cli + - tests: setup_pages_test.go, setup_workflow_golden_test.go, forge_pages_test.go, site_setup_test.go + - landed: 862ace7f8 diff --git a/.abcd/work/issues/open/iss-2610142036270003-on-the-github-pages-hosting-provider-the.md b/.abcd/work/issues/open/iss-2610142036270003-on-the-github-pages-hosting-provider-the.md new file mode 100644 index 0000000000..b1b7b83b13 --- /dev/null +++ b/.abcd/work/issues/open/iss-2610142036270003-on-the-github-pages-hosting-provider-the.md @@ -0,0 +1,15 @@ +--- +schema_version: 1 +id: "iss-2610142036270003" +slug: "on-the-github-pages-hosting-provider-the" +severity: "minor" +category: "security" +source: "impl-review" +found_during: "itd-2610091010348892 build (github-pages provider)" +origin: researcher-authored +production_mode: hand-written +found_at: "internal/adapter/hosting/githubpages/githubpages.go" +remedy: "Carry the declared headers a Pages host can honour into the rendered pages (a CSP meta element per page, generated from site-src/headers), or document that Pages serves none of them and drop _headers from a Pages build." +--- + +On the github-pages hosting provider the site loses the security headers abcd's own site serves: the build copies site-src/headers to _headers, which Cloudflare reads and GitHub Pages ignores, so a project site on GitHub Pages is served without the Content-Security-Policy and the other headers that file declares. From fc68350ac1ab4530fc83184688d177b619bb8f10 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:45:21 +0100 Subject: [PATCH 016/165] chore: pin the session surface in the release-gate manifest The brief surface cross-check pins every surface chapter and command page: adds the session chapter and command, recomputes the prompt hash and the example receipt's manifest hash. The session page tells the user to run the foreground relay in their own Terminal instead of fencing a bare-path command. Assisted-by: Claude:claude-opus-5-5 --- .abcd/development/release-gate/manifest.json | 7 ++++--- .abcd/development/release-gate/receipt.example.json | 2 +- commands/session.md | 11 +++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.abcd/development/release-gate/manifest.json b/.abcd/development/release-gate/manifest.json index 7783899f0a..b8dc588e3e 100644 --- a/.abcd/development/release-gate/manifest.json +++ b/.abcd/development/release-gate/manifest.json @@ -47,6 +47,7 @@ ".abcd/development/brief/04-surfaces/34-build.md", ".abcd/development/brief/04-surfaces/35-drain.md", ".abcd/development/brief/04-surfaces/36-dashboard.md", + ".abcd/development/brief/04-surfaces/37-session.md", ".abcd/development/brief/04-surfaces/README.md", ".abcd/development/brief/02-constraints/04-naming.md", ".abcd/development/brief/05-internals/01-agents.md", @@ -83,10 +84,10 @@ "probe": "list skills/ (abcd ships zero skills; the directory is empty or absent)" } ], - "checkerCount": 41, - "promptHash": "sha256:cbbefe314f294c3517f56b1d6a98886b6d91820db79d1141c8e8718ccc004dad", + "checkerCount": 42, + "promptHash": "sha256:161ab87b7030d9062956439e570809c7f5e5977617cb6e708aad9654702aae59", "prompt": { - "context": "Repo root: the current working directory — use repo-relative paths\nthroughout, never absolute local paths. Ground truth is the SHIPPED surface,\nverified empirically: build the binary (make build produces bin/abcd-<goos>-<arch>)\nand run it (`abcd --help` and `abcd <verb> --help`), and list commands/, agents/\nand skills/. abcd currently ships ZERO skills — the whole /abcd: surface is\ncommands under commands/ (abcd, ahoy, banlist, build, capture, dashboard, decide, disembark, docs, drain, embark, guard,\nhistory, ideate, identity, implement, inbox, intent, lab, launch, library, lint, mode, peers,\nreading, reflect, report, scribe, site, update)\nand agent prompts under agents/ (cold-reading-comparative, cold-reading-detection, cold-reading-entailment,\ncold-reading-widening, docs-currency-reviewer, graveyard-interpreter,\nintent-auditor, lifeboat-reviewer, planning-interviewer, press-release-composer,\nprinciple-distiller, question-drafter, reflection-composer, release-changelog-composer, ruthless-reviewer, scribe,\nsecurity-reviewer, sota-researcher);\nskills/ is empty or absent. The brief chapters that carry surface claims are\npinned in this manifest's briefDocs: .abcd/development/brief/04-surfaces/*.md\n(the index README included) and the 02-constraints, 05-internals and 06-delivery\nchapters that count or enumerate verbs, sub-verbs, agents, hooks and the plugin\ntree. That list says where to LOOK, not what may be REPORTED: a surface claim\nin any other brief chapter is in scope, and a real surface whose only\ndocumented home lies outside the pinned list is reported with that location.\nReport DISCREPANCIES ONLY — where record and reality disagree, or one side is\nmissing. A brief row explicitly marked staged (its Status column is \"staged\")\n/ probe-only / later-phase is NOT a discrepancy; an unmarked claim about a\nsurface that does not exist IS. Do not fix anything.", + "context": "Repo root: the current working directory — use repo-relative paths\nthroughout, never absolute local paths. Ground truth is the SHIPPED surface,\nverified empirically: build the binary (make build produces bin/abcd-<goos>-<arch>)\nand run it (`abcd --help` and `abcd <verb> --help`), and list commands/, agents/\nand skills/. abcd currently ships ZERO skills — the whole /abcd: surface is\ncommands under commands/ (abcd, ahoy, banlist, build, capture, dashboard, decide, disembark, docs, drain, embark, guard,\nhistory, ideate, identity, implement, inbox, intent, lab, launch, library, lint, mode, peers,\nreading, reflect, report, scribe, session, site, update)\nand agent prompts under agents/ (cold-reading-comparative, cold-reading-detection, cold-reading-entailment,\ncold-reading-widening, docs-currency-reviewer, graveyard-interpreter,\nintent-auditor, lifeboat-reviewer, planning-interviewer, press-release-composer,\nprinciple-distiller, question-drafter, reflection-composer, release-changelog-composer, ruthless-reviewer, scribe,\nsecurity-reviewer, sota-researcher);\nskills/ is empty or absent. The brief chapters that carry surface claims are\npinned in this manifest's briefDocs: .abcd/development/brief/04-surfaces/*.md\n(the index README included) and the 02-constraints, 05-internals and 06-delivery\nchapters that count or enumerate verbs, sub-verbs, agents, hooks and the plugin\ntree. That list says where to LOOK, not what may be REPORTED: a surface claim\nin any other brief chapter is in scope, and a real surface whose only\ndocumented home lies outside the pinned list is reported with that location.\nReport DISCREPANCIES ONLY — where record and reality disagree, or one side is\nmissing. A brief row explicitly marked staged (its Status column is \"staged\")\n/ probe-only / later-phase is NOT a discrepancy; an unmarked claim about a\nsurface that does not exist IS. Do not fix anything.", "directionA": "Direction A. Read ${doc} fully. Extract every checkable claim\nabout the shipped surface (verbs, sub-verbs, flags, skill names, counts,\nfile layouts, \"abcd ships N ...\" statements) and verify each against\nreality. Return item=\"${doc}\" and the discrepancy list.", "directionB": "Direction B. The real surface \"${s.name}\" (${s.kind}) exists:\ninspect it (${s.probe}). Search the brief's surface chapters for its\ndocumented home (grep .abcd/development/brief/). If no brief row documents\nit — or the brief documents it under a wrong name/shape — that is a\ndiscrepancy. Return item=\"${s.name}\" and the discrepancy list (empty if\nproperly documented)." } diff --git a/.abcd/development/release-gate/receipt.example.json b/.abcd/development/release-gate/receipt.example.json index 487544a457..0707a06d20 100644 --- a/.abcd/development/release-gate/receipt.example.json +++ b/.abcd/development/release-gate/receipt.example.json @@ -8,7 +8,7 @@ "verificationResult": "PROMOTE", "judgeModel": "claude-opus-4-8", "tier": "full", - "manifestHash": "sha256:a1f18bb638530a6acdc5745d835e2ec799b0709caddce91e1b1a287ceed76cbc", + "manifestHash": "sha256:6d0c24af39f7b3becb5d361ac917fee8d623301bb0b8f006172487f4edd5a861", "policy": { "detector": "iss35-brief-surface-crosscheck", "version": "1", diff --git a/commands/session.md b/commands/session.md index ea0032e4e8..859bea0029 100644 --- a/commands/session.md +++ b/commands/session.md @@ -38,14 +38,9 @@ sessions live in that process's memory alone, so there is no state to report. Starting the relay opens a network listener, so do it only when the user asks to host a session from this computer. It runs in the foreground until stopped -with Ctrl-C, and every session it holds ends with it, so the user runs it in a -Terminal of their own rather than in this session: - -```bash -abcd session serve -``` - -It listens on this computer alone (`127.0.0.1:8787`) and prints one line +with Ctrl-C, and every session it holds ends with it, so never start it from +this session: tell the user to run `abcd session serve` in a Terminal of their +own. It listens on this computer alone (`127.0.0.1:8787`) and prints one line saying where, with the `tailscale serve --bg <port>` command that puts it on the user's tailnet at an https address; relay that line as printed. A non-loopback `--listen` is a second, explicit choice, meant for the team's From 248969ac87bd871a889cd55dffa1cf85236d9a4f Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:45:40 +0100 Subject: [PATCH 017/165] docs: mark the brief session spec's relay and blueprint steps landed Assisted-by: Claude:claude-opus-5-5 --- ...pc-2610141709430146-the-product-thinker-and-the-technical.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index 676ad3ee1b..c40ba8ba10 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -80,9 +80,11 @@ The hosting of abcdesign.app is recorded in the plan but not made in this change 1. The relay: session rooms, join codes, roles, update log, suggestions, sign-in exchange and limits, behind `abcd session serve`, with the WebSocket library github.com/coder/websocket v1.8.15 (ISC, no dependencies) - packages: internal/core/session, internal/surface/session, internal/surface/cli, commands/session.md - tests: internal/core/session, internal/surface/session + - landed: fc68350ac 2. The relay blueprint and the Tailscale route: `abcd session blueprint` writes `.abcd/relay/render.yaml` and `.abcd/relay/Dockerfile`, pinned to a released binary and its checksum - packages: internal/core/session, internal/surface/cli, docs/ - tests: internal/core/session, internal/surface/cli + - landed: fc68350ac 3. The page: `abcd session page --out` emits the static page and the vendored, provenance-pinned editor bundle; joining by QR or code, live editing, presence, guest suggestions and approvals, and credit - packages: internal/surface/session/web, internal/surface/cli - tests: internal/surface/session From 63aef865272c94f26ab66a5317e2627e12077732 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:48:32 +0100 Subject: [PATCH 018/165] docs: record the conjecture behind the per-project Pages site Taken from the intent's accepted Mechanism. Assisted-by: Claude:claude-opus-5-5 --- ...-2610091010348892-anyone-who-opens-abcdev-app-finds-the.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.abcd/development/intents/planned/itd-2610091010348892-anyone-who-opens-abcdev-app-finds-the.md b/.abcd/development/intents/planned/itd-2610091010348892-anyone-who-opens-abcdev-app-finds-the.md index b756ac612b..eaac13aab1 100644 --- a/.abcd/development/intents/planned/itd-2610091010348892-anyone-who-opens-abcdev-app-finds-the.md +++ b/.abcd/development/intents/planned/itd-2610091010348892-anyone-who-opens-abcdev-app-finds-the.md @@ -74,3 +74,7 @@ _Empty. Populated by intent-auditor when intent moves to shipped/._ - 2026-10-14: asked how abcdev.app's list learns which projects switched on, the product thinker set the question aside and reset the shape, verbatim: "this is all too complicated. Let's rethink. if a user opts-in, abcd automatically builds a github.io site with each release. We should test that with the abcd repo at one point so that it no longer has to deploy to cloudflare. The cloudflare abcdev.app will then become a polished version specifically for the abcd community (if there's a need for that); what's important is abcdesign.app, which must work for any abcd-managed repo where a team member can host a session to, initially, edit the brief only, e.g. abcdesign.app/acme/widget/brief/ (later intents/ etc., but not now). That's the most urgent thing for PTs: To be able to edit the brief; the current suggested solution (worktree) isn't ideal; we need the team to live build and/or coordinate here. If they sit in Stoa, or MS-Teams for example and work on that collaboratively." Read for this intent: a project that opts in gets a github.io site built by abcd's workflow at each release, on its own GitHub Pages; abcd's own repository is to be moved onto that route at some point, retiring its Cloudflare deploy; abcdev.app becomes, if there is a need, a polished site for the abcd community only, and no longer lists or renders other projects. The earlier rulings on listing, the opt-in signal, the front-page list and the live view for signed-in contributors are superseded; this intent's press release is rewritten at planning. - 2026-10-14: walked through the rewritten press release above, the product thinker answered "Stands as written". - 2026-10-14: offered the criteria walk on the drafted Why This Matters, Mechanism, Scope Conditions and Acceptance Criteria, the product thinker answered, verbatim: "auto-approve all". Every drafted criterion, the technical ones included, is accepted as written. + +## Grounds + +- pursued: owners switch the project site on because it costs one answer at setup and nothing to run afterwards; shown wrong if owners who say yes find the pages missing or stale after a release From fa690568360f6be1f46a13a1786f9b9c1ce84dd6 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:48:33 +0100 Subject: [PATCH 019/165] docs: record the conjecture behind the live brief session Taken from the intent's accepted Mechanism. Assisted-by: Claude:claude-opus-5-5 --- ...-2610091016262335-the-product-thinker-and-the-technical.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.abcd/development/intents/planned/itd-2610091016262335-the-product-thinker-and-the-technical.md b/.abcd/development/intents/planned/itd-2610091016262335-the-product-thinker-and-the-technical.md index f8c81bf278..b408acaca1 100644 --- a/.abcd/development/intents/planned/itd-2610091016262335-the-product-thinker-and-the-technical.md +++ b/.abcd/development/intents/planned/itd-2610091016262335-the-product-thinker-and-the-technical.md @@ -90,3 +90,7 @@ _Empty. Populated by intent-auditor when intent moves to shipped/._ - 2026-10-14: asked whether team sessions allow private projects in the first release, now that a session runs only on the team's own machine or relay and abcd sees nothing, the product thinker chose "Allow private projects now": the first release serves private repositories as well as public ones, which lifts, for this intent, the same day's "public projects only" ruling (made when the shared copy would have sat on a server abcd runs). The team keeps its own relay safe, and the first release handles sign-in for private repositories. - 2026-10-14: walked through the rewritten press release, the product thinker changed how the work goes back, verbatim: "the team decides: individual edits as issues, credited to the author or approver, or a collective pull request once done". The press release above carries that change; it replaces the earlier "the host sends a small change as a GitHub issue or the whole session as one pull request naming everyone" and the earlier rule that the person picks by the size of the change. - 2026-10-14: offered the criteria walk on the drafted Why This Matters, Mechanism, Scope Conditions and Acceptance Criteria, the product thinker answered, verbatim: "auto-approve all". Every drafted criterion, the technical ones included, is accepted as written. + +## Grounds + +- pursued: a team shapes its brief together during a call because the editing happens where the conversation already is, on any device, with nothing to install but the host's, and returns through the GitHub review the team already uses; shown wrong if teams keep editing the brief afterwards in a terminal session rather than in the call From 6d86fef4ee474476b187836ce37599dd3f993a22 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:53:41 +0100 Subject: [PATCH 020/165] fix: count each tailnet visitor apart behind Tailscale Serve On the host route the relay binds loopback and Tailscale Serve proxies to it, so every visitor's socket address was 127.0.0.1 and one person's wrong codes shut everyone out of joining for ten minutes. On a relay bound to loopback alone, a connection from loopback is now counted by the tailnet address Serve forwards in X-Forwarded-For, accepted only in the shape Serve writes it (one line, one address in Tailscale's ranges); anything else falls back to the peer address. A tailnet IPv6 address is its own key, since every device of a tailnet shares one /64. Assisted-by: Claude:claude-opus-5-5 --- ...6-the-product-thinker-and-the-technical.md | 2 +- commands/session.md | 4 +- docs/how-to/run-a-brief-session-relay.md | 4 +- internal/core/session/codes.go | 24 ++++- internal/core/session/relay_test.go | 17 ++++ internal/surface/cli/session.go | 4 + internal/surface/cli/session_surface_test.go | 56 +++++++++++ internal/surface/session/server.go | 62 +++++++++++-- internal/surface/session/server_test.go | 93 ++++++++++++++++++- 9 files changed, 249 insertions(+), 17 deletions(-) diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index c40ba8ba10..14abaaa716 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -28,7 +28,7 @@ The relay never parses the brief and never writes to disk. **The wire.** A JSON control message is a text frame. A Yjs update or awareness message is a binary frame with a one-byte tag. These messages exist: - `host`: a team member opens a session for owner/repo. The relay answers with a six-character join code from rascals' 31-symbol alphabet. The code works while the host keeps it open, and the host can rotate it. -- `join`: anyone presents the code. Failed joins are rate-limited per address, and the forwarded address is trusted only behind a declared proxy. +- `join`: anyone presents the code. Failed joins are rate-limited per address. The forwarded address is trusted only behind a declared proxy, or, on a relay bound to loopback alone, as Tailscale Serve forwards it: one tailnet address, on a connection from loopback. - `verify`: a participant presents a GitHub token. The relay asks GitHub for the login, and for the repository's `permissions.push`. A participant with write access or above becomes a team member, and the relay drops the token straight after the check. The relay replays the session's update log to every newcomer. It then: diff --git a/commands/session.md b/commands/session.md index 859bea0029..2d6d0775c5 100644 --- a/commands/session.md +++ b/commands/session.md @@ -46,7 +46,9 @@ the user's tailnet at an https address; relay that line as printed. A non-loopback `--listen` is a second, explicit choice, meant for the team's relay; pass it only when the user names the address. `--behind-proxy` trusts the proxy's `X-Forwarded-For` entry as each visitor's address, and belongs to -the team's relay behind its host's proxy. `--origin` admits a page served from +the team's relay behind its host's proxy. On loopback without it, a visitor +is counted by the tailnet address Tailscale Serve forwards, and only when the +connection itself comes from loopback. `--origin` admits a page served from another origin besides `https://abcdesign.app` and loopback. `--json` prints the start line as an object with `url`, `loopback`, `behind_proxy`, `sign_in` and `hint`. diff --git a/docs/how-to/run-a-brief-session-relay.md b/docs/how-to/run-a-brief-session-relay.md index c7a54f1ca1..ae74354ef7 100644 --- a/docs/how-to/run-a-brief-session-relay.md +++ b/docs/how-to/run-a-brief-session-relay.md @@ -105,4 +105,6 @@ Keeping the relay and its secret safe is the team's responsibility. repository, joins as a team member and edits directly. Twenty wrong join codes from one address within ten minutes stop that address -from joining until the ten minutes pass. +from joining until the ten minutes pass. On your own machine, behind Tailscale +Serve, the address is each visitor's own tailnet address, so one teammate's +mistyped codes never lock out the rest. diff --git a/internal/core/session/codes.go b/internal/core/session/codes.go index 6b2d1ffa6c..32f0822855 100644 --- a/internal/core/session/codes.go +++ b/internal/core/session/codes.go @@ -68,18 +68,36 @@ func NormalizeCode(s string) string { return b.String() } +// The address ranges Tailscale gives a tailnet's devices: IPv4 from the CGNAT +// range 100.64.0.0/10 (https://tailscale.com/kb/1015/100.x-addresses) and IPv6 +// from the unique local prefix fd7a:115c:a1e0::/48 +// (https://tailscale.com/kb/1033/ip-and-dns-addresses). Each device holds one +// stable address in each, given by the tailnet, not chosen by the visitor. +var ( + tailnet4 = netip.MustParsePrefix("100.64.0.0/10") + tailnet6 = netip.MustParsePrefix("fd7a:115c:a1e0::/48") +) + +// IsTailnetAddr reports whether a is an address Tailscale gives a device. +func IsTailnetAddr(a netip.Addr) bool { + a = a.Unmap().WithZone("") + return tailnet4.Contains(a) || tailnet6.Contains(a) +} + // AddressKey is the key the failed-join limit counts by: an IPv4 address, or // an IPv6 /64 network, since one home or phone usually holds a whole /64 and // counting single IPv6 addresses would let one visitor rotate through -// millions (the rascals server's addressKey). An address that does not parse -// is its own key. +// millions (the rascals server's addressKey). A tailnet IPv6 address is the +// exception and its own key: every device of a tailnet shares one /64 of +// Tailscale's range, and a device cannot pick another address in it. An +// address that does not parse is its own key. func AddressKey(addr string) string { a, err := netip.ParseAddr(strings.TrimSpace(addr)) if err != nil { return strings.TrimSpace(addr) } a = a.Unmap().WithZone("") - if a.Is4() { + if a.Is4() || tailnet6.Contains(a) { return a.String() } p, err := a.Prefix(64) diff --git a/internal/core/session/relay_test.go b/internal/core/session/relay_test.go index 992adeb160..5c38cf3c0e 100644 --- a/internal/core/session/relay_test.go +++ b/internal/core/session/relay_test.go @@ -646,3 +646,20 @@ func TestSlowPeerIsDropped(t *testing.T) { t.Fatalf("participants = %d, want the slow peer gone", s.Participants) } } + +// TestAddressKeyCountsATailnetAddressAlone: Tailscale gives each device one +// address, and every device of a tailnet shares a /64 of its IPv6 range, so a +// tailnet address is its own key while other IPv6 addresses are counted by /64. +func TestAddressKeyCountsATailnetAddressAlone(t *testing.T) { + for in, want := range map[string]string{ + "fd7a:115c:a1e0::a1": "fd7a:115c:a1e0::a1", + "fd7a:115c:a1e0:ab12:4843:cd96:6258:b240": "fd7a:115c:a1e0:ab12:4843:cd96:6258:b240", + "100.101.102.103": "100.101.102.103", + "2001:db8:1:2::a": "2001:db8:1:2::/64", + "::ffff:192.0.2.1": "192.0.2.1", + } { + if got := AddressKey(in); got != want { + t.Errorf("AddressKey(%q) = %q, want %q", in, got, want) + } + } +} diff --git a/internal/surface/cli/session.go b/internal/surface/cli/session.go index 00c2672652..215499e871 100644 --- a/internal/surface/cli/session.go +++ b/internal/surface/cli/session.go @@ -94,9 +94,13 @@ func newSessionServeCommand(asJSON *bool) *cobra.Command { clientID = strings.TrimSpace(os.Getenv(sessionClientIDEnv)) } secret := os.Getenv(sessionSecretEnv) + // On loopback alone, the relay is the host's own machine behind + // Tailscale Serve, and a visitor is counted by the tailnet address + // Serve forwards. srv, err := session.New(session.Config{ Origins: origins, BehindProxy: behindProxy, + LoopbackOnly: addr.Addr().IsLoopback(), ClientID: clientID, ClientSecret: secret, }) diff --git a/internal/surface/cli/session_surface_test.go b/internal/surface/cli/session_surface_test.go index 1245f2a8da..28b43e256b 100644 --- a/internal/surface/cli/session_surface_test.go +++ b/internal/surface/cli/session_surface_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/coder/websocket" coresession "github.com/intentdriven/abcd/internal/core/session" ) @@ -228,6 +229,61 @@ func TestSessionServeListensOnLoopbackAndStops(t *testing.T) { } } +// TestSessionServeOnLoopbackCountsTailnetVisitorsApart: serve on loopback +// is the host's own machine behind Tailscale Serve, so the wrong codes of one +// tailnet address shut out that address and not every visitor Serve proxies. +func TestSessionServeOnLoopbackCountsTailnetVisitorsApart(t *testing.T) { + out, _ := startServe(t, "--listen", "127.0.0.1:0", "--json") + var started struct { + URL string `json:"url"` + } + if err := json.NewDecoder(strings.NewReader(out.String())).Decode(&started); err != nil { + t.Fatalf("the start line is not JSON: %v\n%s", err, out.String()) + } + wsURL := "ws" + strings.TrimPrefix(started.URL, "http") + "/session" + // join tries a wrong code once on ws and returns the error code the + // relay answers; dial connects as Serve proxies the visitor at addr. + join := func(ws *websocket.Conn) string { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := ws.Write(ctx, websocket.MessageText, []byte(`{"type":"join","code":"ZZZZZZ"}`)); err != nil { + t.Fatalf("write: %v", err) + } + _, data, err := ws.Read(ctx) + if err != nil { + t.Fatalf("read: %v", err) + } + var m map[string]any + _ = json.Unmarshal(data, &m) + code, _ := m["code"].(string) + return code + } + dial := func(addr string) *websocket.Conn { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ws, _, err := websocket.Dial(ctx, wsURL, &websocket.DialOptions{HTTPHeader: http.Header{ + "Origin": {"https://abcdesign.app"}, "X-Forwarded-For": {addr}, + }}) + if err != nil { + t.Fatalf("dial as %s: %v", addr, err) + } + t.Cleanup(func() { _ = ws.CloseNow() }) + return ws + } + mal := dial("100.101.102.103") + for i := 0; i < coresession.DefaultLimits().MaxFailedJoins; i++ { + if got := join(mal); got != "bad-code" { + t.Fatalf("wrong code %d: %q, want bad-code", i, got) + } + } + if got := join(dial("100.101.102.103")); got != "too-many-tries" { + t.Fatalf("the address past its limit: %q, want too-many-tries", got) + } + if got := join(dial("100.64.0.9")); got != "bad-code" { + t.Fatalf("another tailnet address: %q, want bad-code, not held up by the first", got) + } +} + func TestSessionServeTextNamesTheTailnetRoute(t *testing.T) { t.Setenv("ABCD_SESSION_GITHUB_CLIENT_SECRET", "") out, stop := startServe(t, "--listen", "127.0.0.1:0") diff --git a/internal/surface/session/server.go b/internal/surface/session/server.go index 30a4194993..efa75f7265 100644 --- a/internal/surface/session/server.go +++ b/internal/surface/session/server.go @@ -25,6 +25,7 @@ import ( "io" "net" "net/http" + "net/netip" "net/url" "regexp" "strings" @@ -74,6 +75,10 @@ type Config struct { // BehindProxy trusts the last X-Forwarded-For entry as the client's // address, which is right only behind a proxy that sets it. BehindProxy bool + // LoopbackOnly says the listener is bound to loopback alone: the host's + // own machine, reached through Tailscale Serve. Then, and only for a + // loopback peer, the tailnet address Serve forwards is the client's. + LoopbackOnly bool // ClientID is the team's GitHub sign-in app's public id. ClientID string // ClientSecret is that app's secret, read from the environment by the @@ -213,10 +218,23 @@ func (s *Server) originAllowed(origin string) bool { return false } -// clientAddress is the address the failed-join limit counts: the socket's -// peer, or, behind a declared proxy, the last X-Forwarded-For entry (the one -// that proxy added; anything before it the client could have written). -func clientAddress(r *http.Request, behindProxy bool) string { +// clientAddress is the address the failed-join limit counts. +// +// - Behind a declared proxy (--behind-proxy, the team relay), it is the last +// X-Forwarded-For entry: the one that proxy added; anything before it the +// client could have written. +// - On the host's own machine the relay is bound to loopback alone and +// Tailscale Serve proxies to it, so every tailnet visitor's socket address +// is loopback and one visitor's wrong codes would shut out everyone. There, +// and only when the socket's peer is itself loopback, it is the address +// Serve forwards (tailnetForwarded). +// - Otherwise, and whenever the header is missing or malformed, it is the +// socket's peer. +func clientAddress(r *http.Request, behindProxy, loopbackOnly bool) string { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + host = r.RemoteAddr + } if behindProxy { if fwd := r.Header.Values("X-Forwarded-For"); len(fwd) > 0 { parts := strings.Split(fwd[len(fwd)-1], ",") @@ -224,14 +242,42 @@ func clientAddress(r *http.Request, behindProxy bool) string { return last } } + return host } - host, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - return r.RemoteAddr + if loopbackOnly { + if peer, err := netip.ParseAddr(host); err == nil && peer.Unmap().IsLoopback() { + if fwd, ok := tailnetForwarded(r); ok { + return fwd + } + } } return host } +// tailnetForwarded reads the visitor's address as Tailscale Serve forwards +// it. Serve's reverse proxy sets X-Forwarded-For to the tailnet address of the +// device that connected, replacing whatever the device sent, alongside +// X-Forwarded-Host, X-Forwarded-Proto (https) and, for a device that is not +// tagged, the Tailscale-User-Login, Tailscale-User-Name and +// Tailscale-User-Profile-Pic identity headers, which it strips from the +// incoming request first: +// https://tailscale.com/kb/1312/serve (the identity headers) and +// https://github.com/tailscale/tailscale/blob/main/ipn/ipnlocal/serve.go +// (addProxyForwardedHeaders, which sets X-Forwarded-For). So the header is +// accepted only in the shape Serve writes it: one line holding one address +// in Tailscale's ranges. Anything else is not Serve's, and reads as missing. +func tailnetForwarded(r *http.Request) (string, bool) { + fwd := r.Header.Values("X-Forwarded-For") + if len(fwd) != 1 { + return "", false + } + a, err := netip.ParseAddr(strings.TrimSpace(fwd[0])) + if err != nil || a.Zone() != "" || !core.IsTailnetAddr(a) { + return "", false + } + return a.Unmap().String(), true +} + // serveSession upgrades to the session's WebSocket and runs one connection. func (s *Server) serveSession(w http.ResponseWriter, r *http.Request) { if !s.originAllowed(r.Header.Get("Origin")) { @@ -250,7 +296,7 @@ func (s *Server) serveSession(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithCancel(r.Context()) defer cancel() p := &peer{ws: ws, out: make(chan outFrame, sendQueue), cancel: cancel} - conn := s.relay.Connect(p, clientAddress(r, s.cfg.BehindProxy)) + conn := s.relay.Connect(p, clientAddress(r, s.cfg.BehindProxy, s.cfg.LoopbackOnly)) done := make(chan struct{}) go func() { p.writeLoop(ctx); close(done) }() go p.pingLoop(ctx) diff --git a/internal/surface/session/server_test.go b/internal/surface/session/server_test.go index d8757a2e14..b586dc1d68 100644 --- a/internal/surface/session/server_test.go +++ b/internal/surface/session/server_test.go @@ -115,6 +115,21 @@ func (f *fixture) dial(t *testing.T, origin string) *client { return &client{t: t, ws: ws} } +// dialFrom dials as Tailscale Serve would proxy a visitor: with the visitor's +// tailnet address in X-Forwarded-For. +func (f *fixture) dialFrom(t *testing.T, origin, forwarded string) *client { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + h := http.Header{"Origin": {origin}, "X-Forwarded-For": {forwarded}} + ws, _, err := websocket.Dial(ctx, f.wsURL, &websocket.DialOptions{HTTPHeader: h}) + if err != nil { + t.Fatalf("dial from %q as %s: %v", origin, forwarded, err) + } + t.Cleanup(func() { _ = ws.CloseNow() }) + return &client{t: t, ws: ws} +} + func (c *client) sendJSON(v any) { c.t.Helper() data, _ := json.Marshal(v) @@ -411,18 +426,90 @@ func TestClientAddressTrustsForwardedOnlyBehindAProxy(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/session", nil) r.RemoteAddr = "10.0.0.1:5555" r.Header.Set("X-Forwarded-For", "203.0.113.9, 198.51.100.4") - if got := clientAddress(r, false); got != "10.0.0.1" { + if got := clientAddress(r, false, false); got != "10.0.0.1" { t.Errorf("not behind a proxy: %q, want the socket's address", got) } - if got := clientAddress(r, true); got != "198.51.100.4" { + if got := clientAddress(r, true, false); got != "198.51.100.4" { t.Errorf("behind a proxy: %q, want the entry the proxy added", got) } r.Header.Del("X-Forwarded-For") - if got := clientAddress(r, true); got != "10.0.0.1" { + if got := clientAddress(r, true, false); got != "10.0.0.1" { t.Errorf("behind a proxy with no header: %q, want the socket's address", got) } } +// TestClientAddressOnTheTailnetRoute: on the host's own machine the relay +// listens on loopback and Tailscale Serve proxies to it, so every visitor's +// socket address is loopback. Only then is the address Serve forwards the one +// counted; a header from anywhere else, or one Serve would not send, is not. +func TestClientAddressOnTheTailnetRoute(t *testing.T) { + at := func(remote string, xff ...string) *http.Request { + r := httptest.NewRequest(http.MethodGet, "/session", nil) + r.RemoteAddr = remote + for _, v := range xff { + r.Header.Add("X-Forwarded-For", v) + } + return r + } + for _, c := range []struct { + name string + r *http.Request + loopback bool + want string + }{ + {"tailnet IPv4 through Serve", at("127.0.0.1:5555", "100.101.102.103"), true, "100.101.102.103"}, + {"tailnet IPv6 through Serve", at("[::1]:5555", "fd7a:115c:a1e0::a1"), true, "fd7a:115c:a1e0::a1"}, + {"a forged header from a non-loopback peer", at("192.0.2.7:5555", "100.64.0.1"), true, "192.0.2.7"}, + {"a relay not bound to loopback alone", at("127.0.0.1:5555", "100.64.0.1"), false, "127.0.0.1"}, + {"no header", at("127.0.0.1:5555"), true, "127.0.0.1"}, + {"an empty header", at("127.0.0.1:5555", ""), true, "127.0.0.1"}, + {"not an address", at("127.0.0.1:5555", "not-an-ip"), true, "127.0.0.1"}, + {"an address with a port", at("127.0.0.1:5555", "100.64.0.1:443"), true, "127.0.0.1"}, + {"a list Serve never sends", at("127.0.0.1:5555", "100.64.0.1, 100.64.0.2"), true, "127.0.0.1"}, + {"two header lines", at("127.0.0.1:5555", "100.64.0.1", "100.64.0.2"), true, "127.0.0.1"}, + {"an address outside the tailnet", at("127.0.0.1:5555", "203.0.113.9"), true, "127.0.0.1"}, + } { + if got := clientAddress(c.r, false, c.loopback); got != c.want { + t.Errorf("%s: %q, want %q", c.name, got, c.want) + } + } +} + +// TestTailnetVisitorsAreLimitedSeparately: one tailnet visitor's wrong codes +// shut out that visitor alone, not everyone Serve proxies. +func TestTailnetVisitorsAreLimitedSeparately(t *testing.T) { + f := newFixture(t, func(c *Config) { + c.LoopbackOnly = true + c.Limits.MaxFailedJoins = 2 + }) + host := f.dial(t, origin) + host.sendJSON(map[string]string{"type": "verify", "token": "tok-ada", "repo": "acme/brief"}) + host.until("verified") + host.sendJSON(map[string]string{"type": "host", "repo": "acme/brief"}) + hosted, _ := host.until("hosted") + code := hosted["code"].(string) + + for _, pair := range [][2]string{{"100.101.102.103", "100.64.0.9"}, {"fd7a:115c:a1e0::a1", "fd7a:115c:a1e0::b2"}} { + mal := f.dialFrom(t, origin, pair[0]) + for i := 0; i < 2; i++ { + mal.sendJSON(map[string]string{"type": "join", "code": "ZZZZZZ", "name": "Mal"}) + if e, _ := mal.until("error"); e["code"] != "bad-code" { + t.Fatalf("%s try %d: %v, want bad-code", pair[0], i, e) + } + } + again := f.dialFrom(t, origin, pair[0]) + again.sendJSON(map[string]string{"type": "join", "code": code, "name": "Mal"}) + if e, _ := again.until("error"); e["code"] != "too-many-tries" { + t.Fatalf("%s past its limit: %v, want too-many-tries", pair[0], e) + } + other := f.dialFrom(t, origin, pair[1]) + other.sendJSON(map[string]string{"type": "join", "code": code, "name": "Gus"}) + if j, _ := other.until("joined"); j["role"] != "guest" { + t.Fatalf("%s was held up by %s's wrong codes: %v", pair[1], pair[0], j) + } + } +} + func TestOriginsAreParsedStrictly(t *testing.T) { for _, o := range []string{"https://ok.example", "http://ok.example:8080"} { if _, err := New(Config{Origins: []string{o}}); err != nil { From b222d6a2bbf6912f70bd52c643c6c37ef7ec96c1 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:55:10 +0100 Subject: [PATCH 021/165] fix: rate-limit awareness, suggestions and session creation Awareness frames, suggestions and hosting were bounded by counts only. Each connection now has a token bucket for awareness (20 a second, burst 40) and for suggestions (1 a second, burst 5), and one address opens at most 3 sessions in 10 minutes; verify keeps its cap of 5 per connection. A message over its rate is dropped with error rate-limited, and the connection stays open. Assisted-by: Claude:claude-opus-5-5 --- ...6-the-product-thinker-and-the-technical.md | 2 +- docs/how-to/run-a-brief-session-relay.md | 6 + internal/core/session/bucket.go | 29 ++++ internal/core/session/codes.go | 27 ++-- internal/core/session/relay.go | 55 ++++++- internal/core/session/relay_test.go | 139 ++++++++++++++++++ 6 files changed, 242 insertions(+), 16 deletions(-) create mode 100644 internal/core/session/bucket.go diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index 14abaaa716..a770de5dc3 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -36,7 +36,7 @@ The relay replays the session's update log to every newcomer. It then: - forwards a guest's `suggest` message to the team members, stamped with the guest's display name. - broadcasts the roster of verified identities. -Limits bound all of this: message size, log bytes, participants per session, sessions per relay and idle time. Origins are checked against an allowlist, with abcdesign.app and loopback as the defaults. +Limits bound all of this: message size, log bytes, participants per session, sessions per relay and idle time. Rates bound it too: each connection's awareness messages (20 a second, in bursts of 40) and suggestions (1 a second, in bursts of 5), its sign-in attempts (5), and the sessions one address opens (3 in 10 minutes). A message over its rate is dropped with a `rate-limited` error, and the connection stays open. Origins are checked against an allowlist, with abcdesign.app and loopback as the defaults. **Sign-in.** The page builds the GitHub authorize URL from the relay's public client id, with PKCE and a state value it keeps in session storage. The redirect comes back to the page. The page posts the code and the PKCE verifier to `/auth/token`, and the relay adds the secret and does the exchange: this is the token-mediating backend in the IETF's browser-based-apps guidance. The token stays in the page's memory. Pasting a personal access token is the fallback, and the same `verify` admits it. diff --git a/docs/how-to/run-a-brief-session-relay.md b/docs/how-to/run-a-brief-session-relay.md index ae74354ef7..1bf1ffa0a8 100644 --- a/docs/how-to/run-a-brief-session-relay.md +++ b/docs/how-to/run-a-brief-session-relay.md @@ -108,3 +108,9 @@ Twenty wrong join codes from one address within ten minutes stop that address from joining until the ten minutes pass. On your own machine, behind Tailscale Serve, the address is each visitor's own tailnet address, so one teammate's mistyped codes never lock out the rest. + +The relay also bounds how fast anyone can send. One address opens at most three +sessions in ten minutes. On one connection, cursor and selection updates are +held to twenty a second and a guest's suggestions to one a second, each with a +short burst allowed. A message over its limit is dropped with a `rate-limited` +error, and the connection stays open. diff --git a/internal/core/session/bucket.go b/internal/core/session/bucket.go new file mode 100644 index 0000000000..2561e9ca54 --- /dev/null +++ b/internal/core/session/bucket.go @@ -0,0 +1,29 @@ +package session + +import "time" + +// bucket is a token bucket: it holds up to burst tokens, refills at rate +// tokens a second, and each message takes one. Its zero value is full on +// first use. Called under the relay's lock, with the relay's clock. +type bucket struct { + tokens float64 + last time.Time + used bool +} + +// take spends a token if one is left, refilling first for the time since the +// last call. +func (b *bucket) take(now time.Time, rate, burst int) bool { + if !b.used { + b.used, b.tokens, b.last = true, float64(burst), now + } + if elapsed := now.Sub(b.last); elapsed > 0 { + b.tokens = min(float64(burst), b.tokens+elapsed.Seconds()*float64(rate)) + b.last = now + } + if b.tokens < 1 { + return false + } + b.tokens-- + return true +} diff --git a/internal/core/session/codes.go b/internal/core/session/codes.go index 32f0822855..089d37c08f 100644 --- a/internal/core/session/codes.go +++ b/internal/core/session/codes.go @@ -107,9 +107,10 @@ func AddressKey(addr string) string { return p.String() } -// failedJoins counts wrong codes per address within a window, remembering at -// most max addresses (the oldest are forgotten first). -type failedJoins struct { +// addrWindow counts events per address within a window (wrong codes, and +// sessions opened), remembering at most max addresses (the oldest are +// forgotten first). +type addrWindow struct { limit int window time.Duration max int @@ -117,12 +118,12 @@ type failedJoins struct { order []string // insertion order, oldest first } -func newFailedJoins(limit int, window time.Duration, max int) *failedJoins { - return &failedJoins{limit: limit, window: window, max: max, byAddr: map[string][]time.Time{}} +func newAddrWindow(limit int, window time.Duration, max int) *addrWindow { + return &addrWindow{limit: limit, window: window, max: max, byAddr: map[string][]time.Time{}} } -// recent is addr's wrong codes inside the window. -func (f *failedJoins) recent(addr string, now time.Time) []time.Time { +// recent is addr's events inside the window. +func (f *addrWindow) recent(addr string, now time.Time) []time.Time { var out []time.Time for _, t := range f.byAddr[addr] { if now.Sub(t) < f.window { @@ -132,13 +133,13 @@ func (f *failedJoins) recent(addr string, now time.Time) []time.Time { return out } -// blocked reports whether addr has used up its wrong codes. -func (f *failedJoins) blocked(addr string, now time.Time) bool { +// blocked reports whether addr has used up its events. +func (f *addrWindow) blocked(addr string, now time.Time) bool { return len(f.recent(addr, now)) >= f.limit } -// note counts one wrong code from addr and returns its count in the window. -func (f *failedJoins) note(addr string, now time.Time) int { +// note counts one event from addr and returns its count in the window. +func (f *addrWindow) note(addr string, now time.Time) int { list := append(f.recent(addr, now), now) if _, seen := f.byAddr[addr]; !seen { f.order = append(f.order, addr) @@ -154,8 +155,8 @@ func (f *failedJoins) note(addr string, now time.Time) int { return len(list) } -// sweep forgets every address with no wrong code inside the window. -func (f *failedJoins) sweep(now time.Time) { +// sweep forgets every address with no event inside the window. +func (f *addrWindow) sweep(now time.Time) { kept := f.order[:0] for _, a := range f.order { if len(f.recent(a, now)) == 0 { diff --git a/internal/core/session/relay.go b/internal/core/session/relay.go index 49d17f11e5..f714f32904 100644 --- a/internal/core/session/relay.go +++ b/internal/core/session/relay.go @@ -88,6 +88,18 @@ type Limits struct { MaxVerifies int // MaxCredits caps the Yjs client ids one session credits to members. MaxCredits int + // AwarenessPerSecond and AwarenessBurst are one connection's token + // bucket for awareness frames (cursors and selections). + AwarenessPerSecond int + AwarenessBurst int + // SuggestPerSecond and SuggestBurst are one connection's token bucket + // for a guest's suggestions. + SuggestPerSecond int + SuggestBurst int + // MaxHostsPerAddr sessions may be opened from one address within + // HostWindow. + MaxHostsPerAddr int + HostWindow time.Duration } // DefaultLimits are the relay's limits unless a caller sets others. Sixteen @@ -105,6 +117,14 @@ func DefaultLimits() Limits { MaxTrackedAddrs: 10000, MaxVerifies: 5, MaxCredits: 512, + // A cursor moving is a burst of awareness frames, settling at a few a + // second; a guest suggests by hand. + AwarenessPerSecond: 20, + AwarenessBurst: 40, + SuggestPerSecond: 1, + SuggestBurst: 5, + MaxHostsPerAddr: 3, + HostWindow: 10 * time.Minute, } } @@ -142,7 +162,8 @@ type Relay struct { now func() time.Time rand io.Reader rooms map[string]*room // by join code - failed *failedJoins + failed *addrWindow + hosted *addrWindow nextConn int } @@ -163,12 +184,20 @@ func New(o Options) *Relay { fill(&l.MaxTrackedAddrs, d.MaxTrackedAddrs) fill(&l.MaxVerifies, d.MaxVerifies) fill(&l.MaxCredits, d.MaxCredits) + fill(&l.AwarenessPerSecond, d.AwarenessPerSecond) + fill(&l.AwarenessBurst, d.AwarenessBurst) + fill(&l.SuggestPerSecond, d.SuggestPerSecond) + fill(&l.SuggestBurst, d.SuggestBurst) + fill(&l.MaxHostsPerAddr, d.MaxHostsPerAddr) if l.IdleAfter <= 0 { l.IdleAfter = d.IdleAfter } if l.FailedJoinWindow <= 0 { l.FailedJoinWindow = d.FailedJoinWindow } + if l.HostWindow <= 0 { + l.HostWindow = d.HostWindow + } r := &Relay{limits: l, verifier: o.Verifier, now: o.Now, rand: o.Rand, rooms: map[string]*room{}} if r.now == nil { r.now = time.Now @@ -176,7 +205,8 @@ func New(o Options) *Relay { if r.rand == nil { r.rand = rand.Reader } - r.failed = newFailedJoins(l.MaxFailedJoins, l.FailedJoinWindow, l.MaxTrackedAddrs) + r.failed = newAddrWindow(l.MaxFailedJoins, l.FailedJoinWindow, l.MaxTrackedAddrs) + r.hosted = newAddrWindow(l.MaxHostsPerAddr, l.HostWindow, l.MaxTrackedAddrs) return r } @@ -211,6 +241,9 @@ type Conn struct { memberOf string // the repository key the verified login may push to verifies int left bool + // awareness and suggests are the connection's token buckets. + awareness bucket + suggests bucket } // Connect admits a new connection from addr (the client's address as the @@ -341,6 +374,12 @@ func (c *Conn) fail(code, text string) { c.sendJSON(map[string]string{"type": "error", "code": code, "message": text}) } +// rateLimited answers a message dropped for coming too fast. The connection +// stays open: only the message is dropped. +func (c *Conn) rateLimited() { + c.fail("rate-limited", "too many messages of this kind; this one was dropped") +} + // binary handles a tagged Yjs frame, under the lock. func (c *Conn) binary(data []byte) { if c.left { @@ -366,6 +405,10 @@ func (c *Conn) binary(data []byte) { rm.logBytes += len(entry) c.r.forward(rm, c, entry) case data[0] == TagAwareness: + if !c.awareness.take(c.r.now(), c.r.limits.AwarenessPerSecond, c.r.limits.AwarenessBurst) { + c.rateLimited() + return + } c.r.forward(rm, c, append([]byte(nil), data...)) default: c.fail("unknown-tag", "the relay knows update (0) and awareness (1) frames") @@ -481,12 +524,16 @@ func (c *Conn) host(repo string) { case len(r.rooms) >= r.limits.MaxSessions: c.fail("too-many-sessions", "this relay holds as many sessions as it may") return + case r.hosted.blocked(c.addr, r.now()): + c.rateLimited() + return } code, err := r.newCode() if err != nil { c.fail("busy", "no join code could be drawn") return } + r.hosted.note(c.addr, r.now()) rm := &room{repo: repo, repoKey: strings.ToLower(repo), code: code, hostLogin: c.login, credits: map[uint32]string{}} r.rooms[code] = rm c.room, c.role, c.name = rm, roleMember, c.login @@ -554,6 +601,9 @@ func (c *Conn) suggest(body json.RawMessage) { case c.role != roleGuest: c.fail("not-a-guest", "a team member edits directly") return + case !c.suggests.take(c.r.now(), c.r.limits.SuggestPerSecond, c.r.limits.SuggestBurst): + c.rateLimited() + return } trimmed := strings.TrimSpace(string(body)) var obj map[string]json.RawMessage @@ -696,6 +746,7 @@ func (r *Relay) Sweep() { } } r.failed.sweep(now) + r.hosted.sweep(now) } // Stats reports what the relay holds. diff --git a/internal/core/session/relay_test.go b/internal/core/session/relay_test.go index 5c38cf3c0e..434b8d430f 100644 --- a/internal/core/session/relay_test.go +++ b/internal/core/session/relay_test.go @@ -663,3 +663,142 @@ func TestAddressKeyCountsATailnetAddressAlone(t *testing.T) { } } } + +// hostFrom connects at addr, verifies as ada and asks to host acme/brief, +// returning the peer. +func (h *harness) hostFrom(t *testing.T, addr string) *fakePeer { + t.Helper() + p := &fakePeer{} + c := h.r.Connect(p, addr) + send(t, c, msg{"type": "verify", "token": "tok-ada", "repo": "acme/brief"}) + send(t, c, msg{"type": "host", "repo": "acme/brief"}) + return p +} + +// rateLimited reports whether p's last error is rate-limited, with p still +// open: an answer over the limit is dropped, not the connection. +func rateLimited(t *testing.T, p *fakePeer) bool { + t.Helper() + e := p.last(t, "error") + return e != nil && e["code"] == "rate-limited" && !p.closed +} + +func TestAwarenessIsRateLimitedPerConnection(t *testing.T) { + h := newHarness(t, nil) + l := DefaultLimits() + if l.AwarenessPerSecond != 20 || l.AwarenessBurst != 40 { + t.Fatalf("awareness default = %d/s burst %d, want 20/s burst 40", l.AwarenessPerSecond, l.AwarenessBurst) + } + hc, hp, code := h.host(t, "tok-ada") + g, gp := h.join(t, code, "Gus", "198.51.100.7") + hp.reset() + gp.reset() + aw := Frame{Binary: true, Data: []byte{TagAwareness, 1}} + for i := 0; i < l.AwarenessBurst; i++ { + g.Receive(context.Background(), aw) + } + if n := len(hp.binaries()); n != l.AwarenessBurst { + t.Fatalf("the burst forwarded %d frames, want %d", n, l.AwarenessBurst) + } + if e := gp.last(t, "error"); e != nil { + t.Fatalf("an error inside the burst: %v", e) + } + g.Receive(context.Background(), aw) + if n := len(hp.binaries()); n != l.AwarenessBurst { + t.Fatal("a frame past the burst was forwarded") + } + if !rateLimited(t, gp) { + t.Fatalf("past the burst: %v, closed=%v; want rate-limited and still open", gp.last(t, "error"), gp.closed) + } + // A second refills the rate, and no more. + h.clock.advance(time.Second) + hp.reset() + gp.reset() + for i := 0; i < l.AwarenessPerSecond+1; i++ { + g.Receive(context.Background(), aw) + } + if n := len(hp.binaries()); n != l.AwarenessPerSecond { + t.Fatalf("a second later %d frames were forwarded, want %d", n, l.AwarenessPerSecond) + } + if !rateLimited(t, gp) { + t.Fatal("the frame past the refill was not refused") + } + // Each connection has its own bucket. + gp.reset() + hc.Receive(context.Background(), aw) + if len(gp.binaries()) != 1 { + t.Fatal("the host's awareness was held up by the guest's") + } +} + +func TestSuggestIsRateLimitedPerConnection(t *testing.T) { + h := newHarness(t, nil) + l := DefaultLimits() + if l.SuggestPerSecond != 1 || l.SuggestBurst != 5 { + t.Fatalf("suggest default = %d/s burst %d, want 1/s burst 5", l.SuggestPerSecond, l.SuggestBurst) + } + _, hp, code := h.host(t, "tok-ada") + g, gp := h.join(t, code, "Gus", "198.51.100.7") + count := func() int { + n := 0 + for _, m := range hp.texts(t) { + if m["type"] == "suggest" { + n++ + } + } + return n + } + for i := 0; i < l.SuggestBurst+1; i++ { + send(t, g, msg{"type": "suggest", "body": msg{"text": "x"}}) + } + if n := count(); n != l.SuggestBurst { + t.Fatalf("%d suggestions delivered, want the burst of %d", n, l.SuggestBurst) + } + if !rateLimited(t, gp) { + t.Fatalf("past the burst: %v, closed=%v; want rate-limited and still open", gp.last(t, "error"), gp.closed) + } + h.clock.advance(time.Second) + send(t, g, msg{"type": "suggest", "body": msg{"text": "y"}}) + if n := count(); n != l.SuggestBurst+1 { + t.Fatal("a second later the next suggestion was not delivered") + } + gp.reset() + send(t, g, msg{"type": "suggest", "body": msg{"text": "z"}}) + if !rateLimited(t, gp) || count() != l.SuggestBurst+1 { + t.Fatal("the suggestion past the refill was not refused") + } +} + +func TestHostingIsRateLimitedPerAddress(t *testing.T) { + h := newHarness(t, nil) + l := DefaultLimits() + if l.MaxHostsPerAddr != 3 || l.HostWindow != 10*time.Minute { + t.Fatalf("host default = %d per %v, want 3 per 10m", l.MaxHostsPerAddr, l.HostWindow) + } + // A refused host does not count towards the limit. + p := &fakePeer{} + c := h.r.Connect(p, "203.0.113.5") + send(t, c, msg{"type": "host", "repo": "acme/brief"}) + if e := p.last(t, "error"); e == nil || e["code"] != "not-verified" { + t.Fatalf("unverified host: %v", e) + } + for i := 0; i < l.MaxHostsPerAddr; i++ { + if p := h.hostFrom(t, "203.0.113.5"); p.last(t, "hosted") == nil { + t.Fatalf("session %d was not opened: %v", i, p.texts(t)) + } + } + p = h.hostFrom(t, "203.0.113.5") + if p.last(t, "hosted") != nil || !rateLimited(t, p) { + t.Fatalf("a fourth session from one address: %v, closed=%v; want rate-limited and still open", p.texts(t), p.closed) + } + if h.r.Stats().Sessions != l.MaxHostsPerAddr { + t.Fatalf("sessions = %d, want %d", h.r.Stats().Sessions, l.MaxHostsPerAddr) + } + if p := h.hostFrom(t, "198.51.100.7"); p.last(t, "hosted") == nil { + t.Fatal("another address was held up by the limit") + } + h.clock.advance(l.HostWindow) + if p := h.hostFrom(t, "203.0.113.5"); p.last(t, "hosted") == nil { + t.Fatal("the limit outlived its window") + } +} From 4f4511b6196c5e167b3a05a90be84427ebac5e4a Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:55:43 +0100 Subject: [PATCH 022/165] fix: pin the relay Dockerfile's base image by digest The blueprint's Dockerfile built FROM alpine:3.22, a tag the registry can move. It now renders alpine:3.22@sha256:5291449c..., the multi-arch image index digest taken from Docker Hub on 2026-10-14, held as a constant in the blueprint code. The goldens are updated. Assisted-by: Claude:claude-opus-5-5 --- internal/core/session/blueprint.go | 10 +++++++++- internal/core/session/blueprint_test.go | 9 +++++++++ .../core/session/testdata/blueprint/Dockerfile.golden | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/internal/core/session/blueprint.go b/internal/core/session/blueprint.go index cc68c0ac16..6d1940ccca 100644 --- a/internal/core/session/blueprint.go +++ b/internal/core/session/blueprint.go @@ -26,6 +26,14 @@ const BlueprintDir = ".abcd/relay" // linux/amd64. const relayAsset = "abcd-linux-amd64" +// alpineDigest pins the relay image's base, alpine:3.22, to one multi-arch +// image index, so a tag moved on the registry cannot change what the relay is +// built on. Taken on 2026-10-14 from Docker Hub's registry API (the +// Docker-Content-Digest of library/alpine's 3.22 manifest, an OCI image +// index). Renew it with `docker buildx imagetools inspect alpine:3.22` when +// Alpine publishes a fix the relay needs. +const alpineDigest = "sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8" + // releaseOrigin is where abcd's releases are published. const releaseOrigin = "https://github.com/intentdriven/abcd" @@ -122,7 +130,7 @@ func RenderBlueprint(release, digest string) map[string][]byte { # release published (pinned below when this file was written), and runs the # relay behind the host's proxy. abcd refuses to overwrite this file once it is # edited; delete it and run ` + "`abcd session blueprint`" + ` to write it again. -FROM --platform=linux/amd64 alpine:3.22 +FROM --platform=linux/amd64 alpine:3.22@` + alpineDigest + ` RUN wget -q -O /usr/local/bin/abcd "` + releaseOrigin + `/releases/download/` + release + `/` + relayAsset + `" \ && echo "` + digest + ` /usr/local/bin/abcd" | sha256sum -c - \ && chmod 0755 /usr/local/bin/abcd \ diff --git a/internal/core/session/blueprint_test.go b/internal/core/session/blueprint_test.go index b178ce8a02..cef5a64938 100644 --- a/internal/core/session/blueprint_test.go +++ b/internal/core/session/blueprint_test.go @@ -5,6 +5,7 @@ import ( "flag" "os" "path/filepath" + "regexp" "strings" "testing" ) @@ -105,6 +106,14 @@ func TestBlueprintPinsTheReleaseAndItsChecksum(t *testing.T) { if strings.Contains(docker, "ARG ") { t.Error("the pin is a build argument, which a build could override") } + // The base image is pinned by its multi-arch index digest, so a tag moved + // on the registry cannot change what the relay is built on. + if !regexp.MustCompile(`(?m)^FROM --platform=linux/amd64 alpine:3\.22@sha256:[0-9a-f]{64}$`).MatchString(docker) { + t.Errorf("the base image is not pinned by digest:\n%s", docker) + } + if !strings.Contains(docker, "alpine:3.22@"+alpineDigest) { + t.Errorf("the Dockerfile does not pin %s", alpineDigest) + } yaml := string(files["render.yaml"]) for _, want := range []string{ "runtime: docker", "plan: free", "dockerfilePath: ./.abcd/relay/Dockerfile", "healthCheckPath: /healthz", diff --git a/internal/core/session/testdata/blueprint/Dockerfile.golden b/internal/core/session/testdata/blueprint/Dockerfile.golden index 7dbe40727e..ef953c72fd 100644 --- a/internal/core/session/testdata/blueprint/Dockerfile.golden +++ b/internal/core/session/testdata/blueprint/Dockerfile.golden @@ -1,10 +1,10 @@ -# abcd session blueprint v0.14.1 sha256:d476ec6f904ca58647dd45f12ad0efdfc4f77f52c3fa14ef5fb77fb8a89e56a1 +# abcd session blueprint v0.14.1 sha256:9a1777e0d807eb377fa3c609c6c1c1fce568ced0f83d16fd702c8eba64000bc2 # The team's own relay for abcd's live brief sessions. It downloads the # released abcd binary for linux/amd64, checks it against the checksum that # release published (pinned below when this file was written), and runs the # relay behind the host's proxy. abcd refuses to overwrite this file once it is # edited; delete it and run `abcd session blueprint` to write it again. -FROM --platform=linux/amd64 alpine:3.22 +FROM --platform=linux/amd64 alpine:3.22@sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8 RUN wget -q -O /usr/local/bin/abcd "https://github.com/intentdriven/abcd/releases/download/v0.14.1/abcd-linux-amd64" \ && echo "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef /usr/local/bin/abcd" | sha256sum -c - \ && chmod 0755 /usr/local/bin/abcd \ From 4291d5c11a22edeac14473c05f06bb8c257778f2 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:57:16 +0100 Subject: [PATCH 023/165] docs: credit the IETF browser-based apps guidance the sign-in follows The relay's sign-in exchange is the token-mediating backend of OAuth 2.0 for Browser-Based Applications (draft-ietf-oauth-browser-based-apps-27, since RFC 10017). It joins the references, with its CSL-JSON entry. Assisted-by: Claude:claude-opus-5-5 --- .../development/research/references.csl.json | 16 +++++++++++ ACKNOWLEDGEMENTS.md | 28 ++++++++++++------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.abcd/development/research/references.csl.json b/.abcd/development/research/references.csl.json index cb1ad6fd10..3e1be28c61 100644 --- a/.abcd/development/research/references.csl.json +++ b/.abcd/development/research/references.csl.json @@ -252,6 +252,22 @@ "issued": {"date-parts": [[2011, 11, 15]]}, "URL": "https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions" }, + { + "id": "parecki2026browserapps", + "type": "report", + "author": [ + {"family": "Parecki", "given": "Aaron"}, + {"family": "De Ryck", "given": "Philippe"}, + {"family": "Waite", "given": "David"} + ], + "title": "OAuth 2.0 for Browser-Based Applications", + "publisher": "IETF OAuth Working Group", + "genre": "Internet-Draft", + "number": "draft-ietf-oauth-browser-based-apps-27", + "note": "Since published as RFC 10017 (BCP 212)", + "issued": {"date-parts": [[2026, 8]]}, + "URL": "https://datatracker.ietf.org/doc/draft-ietf-oauth-browser-based-apps/27/" + }, { "id": "pearce2022asleep", "type": "paper-conference", diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 7039089b02..dba4bfe5e8 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -451,39 +451,47 @@ CSL-JSON: [`.abcd/development/research/references.csl.json`](.abcd/development/r 19. Michael Nygard. 2011. Documenting architecture decisions. Cognitect blog (15 November 2011). <https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions> -20. Hammond Pearce, Baleegh Ahmad, Benjamin Tan, Brendan Dolan-Gavitt, and +20. Aaron Parecki, Philippe De Ryck, and David Waite. 2026. OAuth 2.0 for + Browser-Based Applications. IETF OAuth Working Group, + draft-ietf-oauth-browser-based-apps-27 (August 2026), since published as + RFC 10017 (BCP 212). Its token-mediating backend pattern (section 6.2) is + the one the brief session relay's sign-in exchange follows: the page holds + the PKCE verifier and receives the token, and the relay adds the client + secret and makes the exchange (spc-2610141709430146). + <https://datatracker.ietf.org/doc/draft-ietf-oauth-browser-based-apps/27/> +21. Hammond Pearce, Baleegh Ahmad, Benjamin Tan, Brendan Dolan-Gavitt, and Ramesh Karri. 2022. Asleep at the keyboard? Assessing the security of GitHub Copilot's code contributions. In *Proceedings of the 43rd IEEE Symposium on Security and Privacy (S&P 2022)*, 754–768. [arXiv:2108.09293](https://arxiv.org/abs/2108.09293) -21. Neil Perry, Megha Srivastava, Deepak Kumar, and Dan Boneh. 2023. Do users +22. Neil Perry, Megha Srivastava, Deepak Kumar, and Dan Boneh. 2023. Do users write more insecure code with AI assistants? In *Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security (CCS '23)*, 2785–2799. [doi:10.1145/3576915.3623157](https://doi.org/10.1145/3576915.3623157) -22. Ranjan Sapkota, Konstantinos I. Roumeliotis, and Manoj Karkee. 2025. Vibe +23. Ranjan Sapkota, Konstantinos I. Roumeliotis, and Manoj Karkee. 2025. Vibe coding vs. agentic coding: Fundamentals and practical implications of agentic AI. [arXiv:2505.19443](https://arxiv.org/abs/2505.19443) -23. Advait Sarkar, Andrew D. Gordon, Carina Negreanu, Christian Poelitz, Sruti +24. Advait Sarkar, Andrew D. Gordon, Carina Negreanu, Christian Poelitz, Sruti Srinivasa Ragavan, and Ben Zorn. 2022. What is it like to program with artificial intelligence? In *Proceedings of the 33rd Annual Conference of the Psychology of Programming Interest Group (PPIG 2022)*. [arXiv:2208.06213](https://arxiv.org/abs/2208.06213) -24. Christopher Scaffidi, Mary Shaw, and Brad A. Myers. 2005. Estimating the +25. Christopher Scaffidi, Mary Shaw, and Brad A. Myers. 2005. Estimating the numbers of end users and end user programmers. In *Proceedings of the 2005 IEEE Symposium on Visual Languages and Human-Centric Computing (VL/HCC '05)*, 207–214. [doi:10.1109/VLHCC.2005.34](https://doi.org/10.1109/VLHCC.2005.34) -25. Donald A. Schön. 1983. *The Reflective Practitioner: How Professionals +26. Donald A. Schön. 1983. *The Reflective Practitioner: How Professionals Think in Action*. Basic Books, New York. <https://openlibrary.org/works/OL3466056W> -26. Shivani Shukla, Himanshu Joshi, and Romilla Syed. 2025. Security +27. Shivani Shukla, Himanshu Joshi, and Romilla Syed. 2025. Security degradation in iterative AI code generation — a systematic analysis of the paradox. In *Proceedings of the 2025 IEEE International Symposium on Technology and Society (ISTAS 2025)*. [arXiv:2506.11022](https://arxiv.org/abs/2506.11022) -27. U.S. Copyright Office. 2025. *Copyright and Artificial Intelligence, +28. U.S. Copyright Office. 2025. *Copyright and Artificial Intelligence, Part 2: Copyrightability*. <https://www.copyright.gov/ai/> -28. Gerald M. Weinberg. 1971. *The Psychology of Computer Programming*. Van +29. Gerald M. Weinberg. 1971. *The Psychology of Computer Programming*. Van Nostrand Reinhold, New York. <https://openlibrary.org/works/OL1958820W> -29. Songwen Zhao, Danqing Wang, Kexun Zhang, Jiaxuan Luo, Zhuo Li, and Lei Li. +30. Songwen Zhao, Danqing Wang, Kexun Zhang, Jiaxuan Luo, Zhuo Li, and Lei Li. 2025. Is vibe coding safe? Benchmarking vulnerability of agent-generated code in real-world tasks. [arXiv:2512.03262](https://arxiv.org/abs/2512.03262) From 5e11171e89e6e1513d0fbab201cd7ee4593a766d Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:19:47 +0100 Subject: [PATCH 024/165] feat: add the brief session page, embedded in the binary The page a team holds its live brief session on (spc-2610141709430146 step 3): index.html with a strict content-security policy and no referrer, session.js and session.css, and the vendored editor bundle. The HTML names its files three levels up, so GitHub Pages' 404 fallback serves it at /<owner>/<repo>/brief/ and a base path works. The page signs a team member in through GitHub with PKCE (or a pasted token, held in memory only), hosts a session, reads the brief through the GitHub API and seeds one shared document once before the QR code and join code show. Members edit live with presence and register their client ids for credit; guests read and suggest a replacement for a selected passage, which a member applies or declines. It rejoins with the same code after a dropped connection. Sending the session back to GitHub is left as the submitSession hook for step 4. package web writes the six files into --out and nothing else, and refuses a directory holding anything it did not write. Assisted-by: Claude:claude-opus-5-5 --- ACKNOWLEDGEMENTS.md | 20 + internal/surface/session/web/index.html | 20 + internal/surface/session/web/session.css | 117 ++ internal/surface/session/web/session.js | 1259 ++++++++++++++++++++++ internal/surface/session/web/web.go | 186 ++++ 5 files changed, 1602 insertions(+) create mode 100644 internal/surface/session/web/index.html create mode 100644 internal/surface/session/web/session.css create mode 100644 internal/surface/session/web/session.js create mode 100644 internal/surface/session/web/web.go diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 7039089b02..29d69389bc 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -353,6 +353,26 @@ Ideas and methodologies that shaped the design — not code abcd depends on. declared proxy; and one free Render web service, deployed from a Blueprint with its secret unsynced, serving every room. `internal/core/session` and `abcd session blueprint` follow it. +- **Yjs, y-protocols and y-codemirror.next (Kevin Jahns and contributors, + MIT), CodeMirror 6 (Marijn Haverbeke and others, MIT) and + qrcode-generator (Kazuhiko Arase, MIT)** — the brief session page's shared + document, presence, Markdown editor and QR code + (`abcd session page`, spc-2610141709430146 step 3), vendored as one + provenance-pinned bundle the binary embeds (adr-2610141949110653); every + bundled package and its licence is listed in + `internal/surface/session/web/vendor/`. <https://yjs.dev> + <https://codemirror.net> <https://github.com/kazuhikoarase/qrcode-generator> +- **The W3C Web Annotation Data Model's TextQuoteSelector** — the shape of a + guest's suggestion on the session page: the quoted passage with up to 32 + characters of context either side, the fallback a team member's page + locates the passage by when the suggestion's relative positions no longer + resolve. <https://www.w3.org/TR/annotation-model/#text-quote-selector> +- **PKCE (RFC 7636) and the IETF's OAuth 2.0 for Browser-Based Applications** + — the session page's GitHub sign-in: an S256 challenge and a random state + held in session storage across the redirect, with the code exchanged by the + team's relay as the token-mediating backend. + <https://www.rfc-editor.org/rfc/rfc7636> + <https://datatracker.ietf.org/doc/draft-ietf-oauth-browser-based-apps/> - **TruffleHog (Truffle Security)** — the optional deeper secret scanner the `scan.deep` recommendation keys on when the binary is present (`internal/core/ahoy`); integrated as an opt-in engine, never bundled. diff --git a/internal/surface/session/web/index.html b/internal/surface/session/web/index.html new file mode 100644 index 0000000000..e0e0630e77 --- /dev/null +++ b/internal/surface/session/web/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src https: wss: http://localhost:* ws://localhost:* http://127.0.0.1:* ws://127.0.0.1:*; base-uri 'none'; form-action 'none'"> +<meta name="referrer" content="no-referrer"> +<meta name="generator" content="abcd session page"> +<title>Brief session + + + + + + +
          +

          This page opens a project's brief for a live session. Open it at /<owner>/<repo>/brief/.

          +
          + + diff --git a/internal/surface/session/web/session.css b/internal/surface/session/web/session.css new file mode 100644 index 0000000000..ccf55887d4 --- /dev/null +++ b/internal/surface/session/web/session.css @@ -0,0 +1,117 @@ +/* The brief session page (spc-2610141709430146 step 3). */ +:root { + --bg: #fbfaf8; + --panel: #ffffff; + --ink: #1d1d1f; + --muted: #5d5d66; + --line: #dddbd5; + --accent: #2f5d9e; + --accent-ink: #ffffff; + --warn: #9a3412; + --ok: #166534; + --radius: 8px; + color-scheme: light dark; +} +@media (prefers-color-scheme: dark) { + :root { + --bg: #161618; + --panel: #1f1f22; + --ink: #ececef; + --muted: #a3a3ad; + --line: #34343a; + --accent: #7aa7e8; + --accent-ink: #0d1726; + --warn: #fdba74; + --ok: #86efac; + } +} +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } +body { + background: var(--bg); + color: var(--ink); + font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif; +} +#app { min-height: 100vh; display: flex; flex-direction: column; } +.notice { max-width: 40rem; margin: 3rem auto; padding: 0 1rem; color: var(--muted); } +[hidden] { display: none !important; } + +/* The bar along the top. */ +.bar { + display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; + padding: .6rem 1rem; border-bottom: 1px solid var(--line); background: var(--panel); +} +.bar h1 { font-size: 1rem; margin: 0; font-weight: 600; } +.bar .spacer { flex: 1; } +.status { font-size: .85rem; color: var(--muted); } +.status.bad { color: var(--warn); } +.who { font-size: .85rem; } + +button, .button { + font: inherit; cursor: pointer; border-radius: var(--radius); + border: 1px solid var(--line); background: var(--panel); color: var(--ink); + padding: .4rem .8rem; +} +button.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); } +button:disabled { opacity: .5; cursor: default; } +input, select, textarea { + font: inherit; color: var(--ink); background: var(--bg); + border: 1px solid var(--line); border-radius: var(--radius); padding: .4rem .5rem; width: 100%; +} +textarea { min-height: 6rem; resize: vertical; } +label { display: block; font-size: .85rem; color: var(--muted); margin: .6rem 0 .2rem; } + +/* The start screen. */ +.start { width: 100%; max-width: 34rem; margin: 2rem auto; padding: 0 1rem; display: grid; gap: 1rem; } +.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; } +.card h2 { font-size: 1rem; margin: 0 0 .3rem; } +.card p { margin: .3rem 0; color: var(--muted); font-size: .9rem; } +.row { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; align-items: center; } + +/* The workspace. */ +.work { flex: 1; display: grid; grid-template-columns: 15rem minmax(0, 1fr) 18rem; min-height: 0; } +.files, .side { border-right: 1px solid var(--line); padding: .8rem; overflow: auto; background: var(--panel); } +.side { border-right: 0; border-left: 1px solid var(--line); display: grid; gap: 1rem; align-content: start; } +.files h2, .side h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 .4rem; } +.files .folder { font-size: .8rem; color: var(--muted); margin: .7rem 0 .2rem; word-break: break-all; } +.files button { + display: block; width: 100%; text-align: left; border: 0; padding: .25rem .4rem; + background: none; font-size: .9rem; word-break: break-all; +} +.files button[aria-current="true"] { background: var(--bg); font-weight: 600; } +.center { display: flex; flex-direction: column; min-width: 0; min-height: 0; } +.filetitle { padding: .5rem 1rem; font-size: .85rem; color: var(--muted); border-bottom: 1px solid var(--line); word-break: break-all; } +.editor { flex: 1; min-height: 20rem; overflow: auto; } +.editor .cm-editor { height: 100%; } +.editor .cm-editor.cm-focused { outline: none; } +.guestbar { border-top: 1px solid var(--line); padding: .6rem 1rem; background: var(--panel); } +.guestbar .quote { white-space: pre-wrap; border-left: 3px solid var(--line); padding-left: .5rem; color: var(--muted); max-height: 8rem; overflow: auto; } + +/* The invite. */ +.invite img { width: 100%; max-width: 14rem; image-rendering: pixelated; display: block; margin: 0 auto; background: #fff; } +.code { font: 600 2.2rem/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .2em; text-align: center; margin: .4rem 0; } +.link { font-size: .75rem; word-break: break-all; color: var(--muted); } + +/* The roster and the suggestions. */ +.roster ul, .suggestions ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; } +.badge { font-size: .7rem; border: 1px solid var(--line); border-radius: 999px; padding: 0 .45rem; margin-left: .3rem; color: var(--muted); } +.badge.host { border-color: var(--accent); color: var(--accent); } +.suggestion { border: 1px solid var(--line); border-radius: var(--radius); padding: .5rem; font-size: .9rem; } +.suggestion .from { font-size: .8rem; color: var(--muted); } +.suggestion del, .suggestion ins { display: block; white-space: pre-wrap; text-decoration: none; padding: .2rem .3rem; border-radius: 4px; margin: .2rem 0; } +.suggestion del { background: rgba(220, 38, 38, .12); } +.suggestion ins { background: rgba(22, 163, 74, .12); } +.state { font-size: .8rem; color: var(--muted); } + +.toast { + position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%); + max-width: min(36rem, calc(100% - 2rem)); background: var(--ink); color: var(--bg); + padding: .6rem 1rem; border-radius: var(--radius); font-size: .9rem; z-index: 10; +} + +@media (max-width: 60rem) { + .work { grid-template-columns: 1fr; } + .files, .side { border: 0; border-bottom: 1px solid var(--line); max-height: none; } + .files { max-height: 12rem; } + .editor { min-height: 60vh; } +} diff --git a/internal/surface/session/web/session.js b/internal/surface/session/web/session.js new file mode 100644 index 0000000000..2aa9cb08d6 --- /dev/null +++ b/internal/surface/session/web/session.js @@ -0,0 +1,1259 @@ +// The brief session page (itd-2610091016262335, spc-2610141709430146 step 3). +// +// A team member hosts a live session on the team's own relay; everyone else +// joins by the QR code or the six-character code. Team members edit the brief +// directly and their edits are credited to them; a guest proposes a +// replacement for a passage, which a team member applies or declines. +// +// What this page talks to, and nothing else: the relay the person names (its +// WebSocket, /config and /auth/token), https://api.github.com with the +// person's own token, and github.com for sign-in. The GitHub token is held in +// this module's memory only; it is never stored, put in a URL or logged. +// Every piece of text from the brief, the relay or another participant is +// rendered with textContent. +import { + Y, Awareness, encodeAwarenessUpdate, applyAwarenessUpdate, removeAwarenessStates, + yCollab, yUndoManagerKeymap, EditorView, basicSetup, EditorState, keymap, markdown, qrcode, +} from "./vendor/collab.js"; + +// --------------------------------------------------------------------------- +// The words the page shows: one table, so they can be reviewed in one place. +// --------------------------------------------------------------------------- +const WORDS = Object.freeze({ + title: "Brief session", + wrongAddress: "This page opens a project's brief for a live session. Open it at ///brief/.", + relayLabel: "Relay address", + relayHint: "The https address of your team's relay, or of the host's computer on Tailscale.", + relayBad: "Give the relay's https address (plain http only for localhost).", + joinHeading: "Join a session", + joinHint: "Type the code the host's screen shows, or scan its QR code.", + codeLabel: "Join code", + nameLabel: "Your name", + join: "Join", + codeBad: "A join code is six letters and digits.", + teamHeading: "Team members", + teamHint: "Sign in with GitHub to edit directly and to host a session. Without signing in you join as a guest and suggest changes for a team member to approve.", + signIn: "Sign in with GitHub", + pasteToken: "Paste a GitHub token instead", + tokenLabel: "Personal access token", + useToken: "Use this token", + signInUnavailable: "This relay has no GitHub sign-in set up. Paste a personal access token instead.", + signInFailed: "GitHub sign-in did not complete. Try again, or paste a personal access token.", + signingIn: "Signing in…", + signedInAs: "Signed in as", + notMember: "GitHub says you cannot change this repository, so you take part as a guest.", + hostHeading: "Host a session", + host: "Host a session", + modeLabel: "Send the result back as", + modePR: "one pull request", + modeIssues: "one issue per change", + loadingBrief: "Reading the brief from GitHub…", + sendingBrief: "Sharing the brief with the session…", + noBrief: "This repository has no brief at .abcd/development/brief/ on its default branch.", + githubFailed: "GitHub did not answer the request for the brief", + connecting: "Connecting…", + connected: "Connected", + reconnecting: "Connection lost. Reconnecting…", + cannotConnect: "The relay could not be reached.", + syncing: "Loading the brief…", + ended: "The session has ended.", + codeGone: "That session is no longer open on this relay.", + inviteHeading: "Invite", + inviteHint: "Scan to join, or type the code.", + newCode: "New code", + endSession: "End session", + endConfirm: "End the session for everyone? Edits not yet sent to GitHub are lost.", + copyLink: "Copy link", + linkCopied: "Link copied.", + filesHeading: "Brief", + noFile: "Choose a file.", + rosterHeading: "In this session", + badgeHost: "host", + badgeMember: "team member", + badgeGuest: "guest", + you: "(you)", + suggestionsHeading: "Suggestions", + noSuggestions: "No suggestions waiting.", + suggestedBy: "Suggested by", + apply: "Apply", + decline: "Decline", + applied: "applied", + declined: "declined", + waiting: "waiting for a team member", + cannotApply: "That passage has changed since it was suggested, so the suggestion cannot be applied.", + guestHint: "You are a guest. Select a passage to suggest a change.", + suggest: "Suggest a change", + selectFirst: "Select a passage first.", + replacementLabel: "Replace it with", + send: "Send suggestion", + cancel: "Cancel", + suggestionSent: "Suggestion sent to the team members.", + noMembers: "No team member is connected to receive it.", + mySuggestionsHeading: "Your suggestions", + submit: "Send to GitHub", + submitComingNext: "Sending the session to GitHub is coming in the next step.", +}); + +// --------------------------------------------------------------------------- +// Fixed values. +// --------------------------------------------------------------------------- +const GITHUB_API = "https://api.github.com"; +const GITHUB_AUTHORIZE = "https://github.com/login/oauth/authorize"; +const BRIEF_DIR = [".abcd", "development", "brief"]; +const BRIEF_PREFIX = BRIEF_DIR.join("/") + "/"; +const SIGN_IN_SCOPE = "repo"; +const TAG_UPDATE = 0; +const TAG_AWARENESS = 1; +// A relay message is at most 256 KiB; the seed is sent in updates of at most +// SEED_BUDGET bytes of text, and a longer file in pieces of SEED_PIECE UTF-16 +// units (three bytes each at most). +const SEED_BUDGET = 160000; +const SEED_PIECE = 50000; +const QUOTE_CONTEXT = 32; +const MAX_REPLACEMENT = 64000; +// Origins of Yjs transactions: what the relay sent, and the host's seeding. +const FROM_RELAY = Symbol("relay"); +const FROM_SEED = Symbol("seed"); +const KEY_RELAY = "abcd.session.relay"; +const KEY_NAME = "abcd.session.name"; +const KEY_SIGN_IN = "abcd.session.sign-in"; +const COLOURS = ["#2f6fdb", "#c2410c", "#15803d", "#9333ea", "#b91c1c", "#0e7490", "#a16207", "#be185d"]; + +// --------------------------------------------------------------------------- +// Storage: only the relay address and the display name are remembered, and +// the sign-in's PKCE verifier waits in session storage across GitHub's +// redirect. Every access may throw (private windows, blocked storage). +// --------------------------------------------------------------------------- +function recall(key) { + try { return localStorage.getItem(key) || ""; } catch { return ""; } +} +function remember(key, value) { + try { localStorage.setItem(key, value); } catch { /* storage unavailable */ } +} +function stashSignIn(pending) { + try { sessionStorage.setItem(KEY_SIGN_IN, JSON.stringify(pending)); return true; } catch { return false; } +} +function takeSignIn() { + try { + const raw = sessionStorage.getItem(KEY_SIGN_IN); + sessionStorage.removeItem(KEY_SIGN_IN); + return raw ? JSON.parse(raw) : null; + } catch { return null; } +} + +// --------------------------------------------------------------------------- +// The GitHub token: module memory only. +// --------------------------------------------------------------------------- +let githubToken = ""; + +// --------------------------------------------------------------------------- +// Small DOM helpers. Text always goes in through textContent. +// --------------------------------------------------------------------------- +function el(tag, props = {}, ...children) { + const node = document.createElement(tag); + for (const [k, v] of Object.entries(props)) { + if (v === undefined || v === null || v === false) continue; + if (k === "class") node.className = v; + else if (k === "text") node.textContent = v; + else if (k.startsWith("on")) node.addEventListener(k.slice(2), v); + else node.setAttribute(k, v === true ? "" : String(v)); + } + for (const c of children) { + if (c === null || c === undefined || c === false) continue; + node.append(typeof c === "string" ? document.createTextNode(c) : c); + } + return node; +} +function clear(node) { + while (node.firstChild) node.removeChild(node.firstChild); +} +let toastTimer = 0; +function toast(text) { + const t = ui.toast; + t.textContent = text; + t.hidden = false; + clearTimeout(toastTimer); + toastTimer = setTimeout(() => { t.hidden = true; }, 6000); +} + +// --------------------------------------------------------------------------- +// Where the page is: ///brief/, under any base path. +// --------------------------------------------------------------------------- +const NAME_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/; +const REPO_RE = /^[A-Za-z0-9._-]{1,100}$/; +function readRoute() { + const m = /^(.*)\/([^/]+)\/([^/]+)\/brief\/?$/.exec(location.pathname); + if (!m) return null; + let owner, repo; + try { owner = decodeURIComponent(m[2]); repo = decodeURIComponent(m[3]); } catch { return null; } + if (!NAME_RE.test(owner) || !REPO_RE.test(repo) || repo === "." || repo === "..") return null; + return { base: m[1], owner, repo, slug: owner + "/" + repo }; +} +function pageURL() { + return location.origin + location.pathname; +} + +// relayOrigin reads a relay address: https, or http on this computer. +function relayOrigin(input) { + const s = String(input || "").trim(); + if (!s) return ""; + let u; + try { u = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(s) ? s : "https://" + s); } catch { return ""; } + if (u.username || u.password) return ""; + const local = u.hostname === "localhost" || u.hostname === "127.0.0.1"; + if (u.protocol === "https:" || (u.protocol === "http:" && local)) return u.origin; + return ""; +} +const CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; +function normalizeCode(s) { + const c = String(s || "").toUpperCase().replace(/[\s-]/g, ""); + if (c.length !== 6) return ""; + for (const ch of c) if (!CODE_ALPHABET.includes(ch)) return ""; + return c; +} + +// --------------------------------------------------------------------------- +// The shared document. gc stays off so every change can be credited. +// --------------------------------------------------------------------------- +const doc = new Y.Doc({ gc: false }); +const files = doc.getMap("files"); +const meta = doc.getMap("meta"); +const approvals = doc.getArray("approvals"); +const suggestionsDone = doc.getMap("suggestions_done"); +const awareness = new Awareness(doc); + +// --------------------------------------------------------------------------- +// Page state. +// --------------------------------------------------------------------------- +const route = readRoute(); +const session = { + relay: "", + code: "", + name: "", + login: "", + member: false, // GitHub says this login may push + role: "", // "member" or "guest", once in the session + host: false, + myId: "", + inRoom: false, // joined or hosted on the current socket + everJoined: false, // reconnect only once a session was reached + seeding: false, + synced: false, + over: false, + verifying: false, + pending: "", // "host" when the host button waits for a socket + roster: [], + credits: {}, // Yjs client id -> login, from the relay's roster +}; +const incoming = new Map(); // suggestion id -> {id, from, body}, members only +const mine = []; // a guest's own suggestions +const awaitingAck = []; // suggestions sent, waiting for "suggested" +let ws = null; +let retries = 0; +let retryTimer = 0; +let outbox = []; // a member's updates made while disconnected + +// --------------------------------------------------------------------------- +// The relay connection. +// --------------------------------------------------------------------------- +function socketOpen() { + return ws !== null && ws.readyState === WebSocket.OPEN; +} +function sendJSON(msg) { + if (socketOpen()) ws.send(JSON.stringify(msg)); +} +function sendBinary(tag, bytes) { + const frame = new Uint8Array(bytes.length + 1); + frame[0] = tag; + frame.set(bytes, 1); + if (socketOpen() && session.inRoom) { + ws.send(frame); + return true; + } + return false; +} + +function openSocket() { + if (ws !== null || session.over || !session.relay) return; + setStatus(session.everJoined ? WORDS.reconnecting : WORDS.connecting, session.everJoined); + const url = session.relay.replace(/^http/, "ws") + "/session"; + const sock = new WebSocket(url); + sock.binaryType = "arraybuffer"; + ws = sock; + sock.onopen = () => { if (ws === sock) afterOpen(); }; + sock.onmessage = (ev) => { if (ws === sock) receive(ev.data); }; + sock.onclose = (ev) => { if (ws === sock) socketClosed(ev); }; +} + +function afterOpen() { + if (githubToken) { + session.verifying = true; + sendJSON({ type: "verify", token: githubToken, repo: route.slug }); + return; + } + proceed(); +} + +// proceed joins or hosts once the connection is open and any sign-in checked. +function proceed() { + if (session.code) { + sendJSON({ type: "join", code: session.code, name: session.name }); + } else if (session.pending === "host") { + session.pending = ""; + sendJSON({ type: "host", repo: route.slug }); + } + renderStart(); +} + +function socketClosed(ev) { + ws = null; + session.inRoom = false; + session.verifying = false; + renderTop(); + if (session.over) return; + if (ev.code === 1008) { + endSession(ev.reason ? ev.reason : WORDS.cannotConnect); + return; + } + if (!session.everJoined) { + setStatus(WORDS.cannotConnect, true); + renderStart(); + return; + } + setStatus(WORDS.reconnecting, true); + const delay = Math.min(30000, 1000 * 2 ** retries) * (0.5 + Math.random() / 2); + retries += 1; + clearTimeout(retryTimer); + retryTimer = setTimeout(openSocket, delay); +} + +function endSession(text) { + session.over = true; + session.inRoom = false; + clearTimeout(retryTimer); + if (ws) { + const sock = ws; + ws = null; + try { sock.close(); } catch { /* already closed */ } + } + setStatus(text, true); + toast(text); + renderAll(); +} + +function receive(data) { + if (data instanceof ArrayBuffer) { + const bytes = new Uint8Array(data); + if (bytes.length === 0) return; + const body = bytes.subarray(1); + try { + if (bytes[0] === TAG_UPDATE) Y.applyUpdate(doc, body, FROM_RELAY); + else if (bytes[0] === TAG_AWARENESS) applyAwarenessUpdate(awareness, body, FROM_RELAY); + } catch { + // A frame that does not decode is dropped; the relay forwards only + // what a participant's page sent. + } + return; + } + let msg; + try { msg = JSON.parse(data); } catch { return; } + if (!msg || typeof msg.type !== "string") return; + switch (msg.type) { + case "verified": onVerified(msg); break; + case "hosted": onHosted(msg); break; + case "joined": onJoined(msg); break; + case "synced": onSynced(); break; + case "roster": onRoster(msg); break; + case "suggest": onSuggest(msg); break; + case "suggested": onSuggested(msg); break; + case "code": onCode(msg); break; + case "closed": endSession(WORDS.ended); break; + case "error": onError(msg); break; + default: break; + } +} + +function onVerified(msg) { + const wasVerifying = session.verifying; + session.verifying = false; + session.login = typeof msg.login === "string" ? msg.login : ""; + session.member = msg.member === true; + if (session.inRoom) { + // A participant who joined by code and then signed in. + if (session.member) becomeMember(); + else toast(WORDS.notMember); + renderAll(); + return; + } + if (!session.member && !session.code) toast(WORDS.notMember); + if (wasVerifying) proceed(); + renderAll(); +} + +function onHosted(msg) { + session.code = normalizeCode(msg.code); + session.myId = String(msg.id || ""); + session.host = true; + session.role = "member"; + session.inRoom = true; + session.everJoined = true; + session.seeding = true; + retries = 0; + setStatus(WORDS.connected, false); + sendJSON({ type: "register", client: doc.clientID }); + renderAll(); + hostBrief().catch((err) => { + // A session that cannot be seeded is closed again: nobody has its code. + sendJSON({ type: "close" }); + session.seeding = false; + endSession(err instanceof Error && err.message ? err.message : WORDS.githubFailed); + }); +} + +function onJoined(msg) { + session.myId = String(msg.id || ""); + session.role = msg.role === "member" ? "member" : "guest"; + session.host = msg.host === true; + if (typeof msg.name === "string" && session.role === "guest") session.name = msg.name; + session.inRoom = true; + session.everJoined = true; + retries = 0; + setStatus(session.synced ? WORDS.connected : WORDS.syncing, false); + renderAll(); +} + +function onSynced() { + if (session.seeding) return; // the host's own fresh session: the seed follows + session.synced = true; + setStatus(WORDS.connected, false); + if (session.role === "member") becomeMember(); + shareAwareness(); + renderAll(); +} + +function becomeMember() { + const was = session.role; + session.role = "member"; + sendJSON({ type: "register", client: doc.clientID }); + const queued = outbox; + outbox = []; + for (const frame of queued) { + if (!sendBinary(TAG_UPDATE, frame)) outbox.push(frame); + } + if (was !== "member") openFile(currentPath); +} + +function onRoster(msg) { + const before = new Set(session.roster.map((p) => p.id)); + session.roster = Array.isArray(msg.participants) ? msg.participants.filter((p) => p && typeof p.id === "string") : []; + session.credits = msg.clients && typeof msg.clients === "object" ? msg.clients : {}; + for (const p of session.roster) { + if (p.id === session.myId) { + session.host = p.host === true; + if (p.role === "member" && session.role !== "member" && session.synced) becomeMember(); + } + } + // A newcomer sees the others' cursors once they say where they are. + if (session.roster.some((p) => !before.has(p.id))) shareAwareness(); + renderSide(); + renderTop(); +} + +function onCode(msg) { + const code = normalizeCode(msg.code); + if (code) { + session.code = code; + updateFragment(); + renderSide(); + } +} + +function onError(msg) { + const code = typeof msg.code === "string" ? msg.code : ""; + const text = typeof msg.message === "string" ? msg.message : code; + if (session.verifying) { + session.verifying = false; + githubToken = ""; + toast(text); + proceed(); + return; + } + if (code === "bad-code" || code === "too-many-tries") { + if (session.everJoined) endSession(WORDS.codeGone); + else { + // The connection stays open: the person can type the code again. + session.code = ""; + toast(text); + renderStart(); + } + return; + } + toast(text); +} + +// --------------------------------------------------------------------------- +// Yjs updates and awareness on the wire. +// --------------------------------------------------------------------------- +doc.on("update", (update, origin) => { + if (origin === FROM_RELAY) return; + if (session.role !== "member") return; // a guest's text never changes locally + if (!sendBinary(TAG_UPDATE, update)) outbox.push(update); +}); + +function shareAwareness() { + if (awareness.getLocalState() === null) return; + sendBinary(TAG_AWARENESS, encodeAwarenessUpdate(awareness, [doc.clientID])); +} +awareness.on("update", ({ added, updated, removed }, origin) => { + if (origin === FROM_RELAY) return; + const changed = added.concat(updated, removed); + if (!changed.includes(doc.clientID)) return; + sendBinary(TAG_AWARENESS, encodeAwarenessUpdate(awareness, [doc.clientID])); +}); +window.addEventListener("pagehide", () => { + removeAwarenessStates(awareness, [doc.clientID], "window unload"); +}); +function setAwarenessUser() { + const name = session.role === "member" && session.login ? session.login : (session.name || "Guest"); + awareness.setLocalStateField("user", { name, color: COLOURS[doc.clientID % COLOURS.length] }); +} + +// --------------------------------------------------------------------------- +// Sign-in: GitHub's authorize page with PKCE, then the relay's exchange; or a +// pasted personal access token. Either way the token stays in memory. +// --------------------------------------------------------------------------- +function b64url(bytes) { + let s = ""; + for (const b of bytes) s += String.fromCharCode(b); + return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} +function randomText(n) { + return b64url(crypto.getRandomValues(new Uint8Array(n))); +} +function isLoopbackOrigin(origin) { + try { + const h = new URL(origin).hostname; + return h === "localhost" || h === "127.0.0.1"; + } catch { return false; } +} + +async function relayConfig() { + const res = await fetch(session.relay + "/config", { + method: "GET", credentials: "omit", cache: "no-store", referrerPolicy: "no-referrer", + }); + if (!res.ok) throw new Error(WORDS.cannotConnect); + const cfg = await res.json(); + const clientID = typeof cfg.client_id === "string" ? cfg.client_id : ""; + let authorize = typeof cfg.authorize_url === "string" ? cfg.authorize_url : ""; + // Sign-in goes to github.com alone; a loopback relay may name a local + // stand-in for testing. + if (authorize !== GITHUB_AUTHORIZE && !(isLoopbackOrigin(session.relay) && isLoopbackOrigin(authorize))) authorize = ""; + return { + signIn: cfg.sign_in === true && /^[A-Za-z0-9._-]{1,100}$/.test(clientID) && authorize !== "", + clientID, + authorize, + }; +} + +async function startSignIn() { + if (!readRelayInput()) return; + if (!session.everJoined) { + session.code = normalizeCode(ui.code.value); + session.name = ui.name.value.trim().slice(0, 40); + } + let cfg; + try { cfg = await relayConfig(); } catch { toast(WORDS.cannotConnect); return; } + if (!cfg.signIn) { + toast(WORDS.signInUnavailable); + showTokenForm(true); + return; + } + const state = randomText(16); + const verifier = randomText(32); + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)); + const pending = { state, verifier, relay: session.relay, code: session.code, name: session.name }; + if (!stashSignIn(pending)) { + toast(WORDS.signInFailed); + showTokenForm(true); + return; + } + const q = new URLSearchParams({ + client_id: cfg.clientID, + redirect_uri: pageURL(), + scope: SIGN_IN_SCOPE, + state, + code_challenge: b64url(new Uint8Array(digest)), + code_challenge_method: "S256", + allow_signup: "false", + }); + location.assign(cfg.authorize + "?" + q.toString()); +} + +// finishSignIn handles GitHub's redirect back to this page. +async function finishSignIn() { + const q = new URLSearchParams(location.search); + if (!q.has("state") || (!q.has("code") && !q.has("error"))) return false; + const code = q.get("code") || ""; + const state = q.get("state") || ""; + const pending = takeSignIn(); + // The code and state come off the address bar before anything else. + history.replaceState(null, "", location.pathname + location.hash); + if (!pending || typeof pending.state !== "string" || pending.state !== state || !code) { + toast(WORDS.signInFailed); + return true; + } + session.relay = relayOrigin(pending.relay); + session.code = normalizeCode(pending.code); + session.name = typeof pending.name === "string" ? pending.name : ""; + if (session.code) updateFragment(); + if (!session.relay) { toast(WORDS.signInFailed); return true; } + setStatus(WORDS.signingIn, false); + try { + const res = await fetch(session.relay + "/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code, code_verifier: pending.verifier, redirect_uri: pageURL() }), + credentials: "omit", cache: "no-store", referrerPolicy: "no-referrer", + }); + const out = await res.json().catch(() => ({})); + if (!res.ok || typeof out.access_token !== "string" || out.access_token === "") throw new Error("exchange"); + githubToken = out.access_token; + } catch { + setStatus("", false); + toast(WORDS.signInFailed); + return true; + } + connectSignedIn(); + return true; +} + +function useToken(value) { + const t = String(value || "").trim(); + if (!t) return; + if (!readRelayInput()) return; + githubToken = t; + connectSignedIn(); +} + +// connectSignedIn presents the token: on the open connection when there is +// one, or on a new one. +function connectSignedIn() { + setStatus(WORDS.signingIn, false); + if (socketOpen()) { + session.verifying = !session.inRoom; + sendJSON(session.inRoom ? { type: "verify", token: githubToken } : { type: "verify", token: githubToken, repo: route.slug }); + } else { + openSocket(); + } + renderAll(); +} + +// --------------------------------------------------------------------------- +// Hosting: read the brief through GitHub's API, seed the document once, then +// show the invite. +// --------------------------------------------------------------------------- +async function gh(path) { + const res = await fetch(GITHUB_API + path, { + headers: { Accept: "application/vnd.github+json", Authorization: "Bearer " + githubToken }, + credentials: "omit", cache: "no-store", referrerPolicy: "no-referrer", + }); + if (!res.ok) throw new Error(WORDS.githubFailed + " (" + res.status + ")."); + return res.json(); +} +const SHA_RE = /^[0-9a-f]{40,64}$/; +function repoPath() { + return "/repos/" + encodeURIComponent(route.owner) + "/" + encodeURIComponent(route.repo); +} +async function subtree(sha, name, recursive) { + if (!SHA_RE.test(sha)) throw new Error(WORDS.noBrief); + const t = await gh(repoPath() + "/git/trees/" + sha + (recursive ? "?recursive=1" : "")); + if (!t || !Array.isArray(t.tree)) throw new Error(WORDS.noBrief); + if (recursive && t.truncated) throw new Error(WORDS.noBrief); + if (name === null) return t; + const entry = t.tree.find((e) => e && e.path === name && e.type === "tree"); + if (!entry) throw new Error(WORDS.noBrief); + return entry.sha; +} +function decodeBlob(b64) { + const bin = atob(String(b64).replace(/\s/g, "")); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return new TextDecoder("utf-8", { fatal: true }).decode(bytes); +} +async function loadBrief() { + const repo = await gh(repoPath()); + const branch = typeof repo.default_branch === "string" ? repo.default_branch : ""; + if (!branch) throw new Error(WORDS.noBrief); + const head = await gh(repoPath() + "/branches/" + encodeURIComponent(branch)); + const sha = head && head.commit && head.commit.sha; + const rootTree = head && head.commit && head.commit.commit && head.commit.commit.tree && head.commit.commit.tree.sha; + if (!SHA_RE.test(sha || "") || !SHA_RE.test(rootTree || "")) throw new Error(WORDS.noBrief); + let treeSha = rootTree; + for (const name of BRIEF_DIR) treeSha = await subtree(treeSha, name, false); + const brief = await subtree(treeSha, null, true); + const blobs = brief.tree.filter((e) => e && e.type === "blob" && typeof e.path === "string" && e.path.endsWith(".md") && SHA_RE.test(e.sha || "")); + if (blobs.length === 0) throw new Error(WORDS.noBrief); + const out = new Array(blobs.length); + let next = 0; + async function worker() { + while (next < blobs.length) { + const i = next++; + const b = await gh(repoPath() + "/git/blobs/" + blobs[i].sha); + if (!b || b.encoding !== "base64") throw new Error(WORDS.noBrief); + out[i] = { path: BRIEF_PREFIX + blobs[i].path, text: decodeBlob(b.content) }; + } + } + await Promise.all([worker(), worker(), worker(), worker(), worker(), worker()]); + out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); + return { sha, branch, files: out }; +} + +function utf8Length(s) { + return new TextEncoder().encode(s).length; +} +function pieces(text, size) { + const out = []; + let i = 0; + while (i < text.length) { + let end = Math.min(text.length, i + size); + // Never split a surrogate pair. + if (end < text.length && /[\uD800-\uDBFF]/.test(text[end - 1])) end -= 1; + out.push(text.slice(i, end)); + i = end; + } + return out; +} + +// seed fills the document from the brief, exactly once. Each transaction is +// one update the relay logs, kept under its message size. +function seed(brief, mode) { + if (files.size > 0 || meta.has("base_sha")) return; + let batch = []; + let bytes = 0; + const flush = () => { + if (batch.length === 0) return; + const work = batch; + batch = []; + bytes = 0; + doc.transact(() => { for (const f of work) files.set(f.path, new Y.Text(f.text)); }, FROM_SEED); + }; + for (const f of brief.files) { + const size = utf8Length(f.text); + if (size <= SEED_BUDGET) { + if (bytes + size > SEED_BUDGET) flush(); + batch.push(f); + bytes += size; + continue; + } + flush(); + const text = new Y.Text(); + doc.transact(() => { files.set(f.path, text); }, FROM_SEED); + for (const p of pieces(f.text, SEED_PIECE)) doc.transact(() => { text.insert(text.length, p); }, FROM_SEED); + } + flush(); + const base = Y.encodeSnapshot(Y.snapshot(doc)); + doc.transact(() => { + meta.set("repo", route.slug); + meta.set("base_sha", brief.sha); + meta.set("branch", brief.branch); + meta.set("mode", mode === "issues" ? "issues" : "pr"); + meta.set("base_snapshot", base); + }, FROM_SEED); +} + +async function drained() { + for (let i = 0; i < 600 && socketOpen() && ws.bufferedAmount > 0; i++) { + await new Promise((r) => setTimeout(r, 50)); + } +} + +async function hostBrief() { + setStatus(WORDS.loadingBrief, false); + const brief = await loadBrief(); + setStatus(WORDS.sendingBrief, false); + seed(brief, ui.mode ? ui.mode.value : "pr"); + await drained(); + session.seeding = false; + session.synced = true; + updateFragment(); + setStatus(WORDS.connected, false); + renderAll(); +} + +function inviteURL() { + return location.origin + route.base + "/" + encodeURIComponent(route.owner) + "/" + encodeURIComponent(route.repo) + + "/brief/#relay=" + encodeURIComponent(session.relay) + "&code=" + session.code; +} +function updateFragment() { + if (!session.code || !session.relay) return; + history.replaceState(null, "", location.pathname + "#relay=" + encodeURIComponent(session.relay) + "&code=" + session.code); +} + +// --------------------------------------------------------------------------- +// Guests' suggestions, and the members' answers to them. +// --------------------------------------------------------------------------- +function quoteOf(text, from, to) { + return { + exact: text.slice(from, to), + prefix: text.slice(Math.max(0, from - QUOTE_CONTEXT), from), + suffix: text.slice(to, to + QUOTE_CONTEXT), + }; +} + +function sendSuggestion(replacement) { + if (!view || !currentPath) return; + const ytext = files.get(currentPath); + const sel = view.state.selection.main; + if (!(ytext instanceof Y.Text) || sel.empty) { toast(WORDS.selectFirst); return; } + if (replacement.length > MAX_REPLACEMENT) return; + const text = ytext.toString(); + const body = { + path: currentPath, + anchor: Y.relativePositionToJSON(Y.createRelativePositionFromTypeIndex(ytext, sel.from)), + head: Y.relativePositionToJSON(Y.createRelativePositionFromTypeIndex(ytext, sel.to)), + quote: quoteOf(text, sel.from, sel.to), + replacement, + }; + awaitingAck.push(body); + sendJSON({ type: "suggest", body }); +} + +function onSuggested(msg) { + const body = awaitingAck.shift(); + if (!body) return; + mine.push({ id: String(msg.id || ""), body }); + toast(msg.delivered > 0 ? WORDS.suggestionSent : WORDS.noMembers); + renderSide(); +} + +function validSuggestion(b) { + return b && typeof b === "object" && typeof b.path === "string" && typeof b.replacement === "string" && + b.replacement.length <= MAX_REPLACEMENT && b.quote && typeof b.quote === "object" && + typeof b.quote.exact === "string" && b.quote.exact !== "" && + typeof (b.quote.prefix ?? "") === "string" && typeof (b.quote.suffix ?? "") === "string"; +} + +function onSuggest(msg) { + if (session.role !== "member") return; + const id = String(msg.id || ""); + if (!id || !validSuggestion(msg.body)) return; + incoming.set(id, { id, from: typeof msg.from === "string" ? msg.from : "Guest", body: msg.body }); + renderSide(); +} + +// findQuote places a W3C TextQuoteSelector: the occurrence of exact whose +// surroundings match the prefix and suffix best, or -1 when that is not one. +function findQuote(text, q) { + const at = []; + for (let i = text.indexOf(q.exact); i !== -1; i = text.indexOf(q.exact, i + 1)) at.push(i); + if (at.length === 0) return -1; + if (at.length === 1) return at[0]; + const prefix = q.prefix || ""; + const suffix = q.suffix || ""; + const score = (i) => { + let s = 0; + const before = text.slice(Math.max(0, i - prefix.length), i); + const after = text.slice(i + q.exact.length, i + q.exact.length + suffix.length); + while (s < prefix.length && before[before.length - 1 - s] === prefix[prefix.length - 1 - s]) s++; + let t = 0; + while (t < suffix.length && after[t] === suffix[t]) t++; + return s + t; + }; + const scored = at.map((i) => [score(i), i]).sort((a, b) => b[0] - a[0]); + return scored[0][0] > scored[1][0] ? scored[0][1] : -1; +} + +// locate finds the suggested passage now: its relative positions first, then +// its quote; null when neither still points at the quoted text. +function locate(body) { + const ytext = files.get(body.path); + if (!(ytext instanceof Y.Text)) return null; + const text = ytext.toString(); + try { + const a = Y.createAbsolutePositionFromRelativePosition(Y.createRelativePositionFromJSON(body.anchor), doc); + const h = Y.createAbsolutePositionFromRelativePosition(Y.createRelativePositionFromJSON(body.head), doc); + if (a && h && a.type === ytext && h.type === ytext) { + const from = Math.min(a.index, h.index); + const to = Math.max(a.index, h.index); + if (text.slice(from, to) === body.quote.exact) return { ytext, from, to }; + } + } catch { /* fall through to the quote */ } + const at = findQuote(text, body.quote); + return at < 0 ? null : { ytext, from: at, to: at + body.quote.exact.length }; +} + +function applySuggestion(id) { + const s = incoming.get(id); + if (!s || session.role !== "member" || suggestionsDone.has(id)) return; + const place = locate(s.body); + if (!place) { toast(WORDS.cannotApply); return; } + doc.transact(() => { + const before = place.ytext.toString().slice(place.from, place.to); + place.ytext.delete(place.from, place.to - place.from); + place.ytext.insert(place.from, s.body.replacement); + approvals.push([{ + id, + path: s.body.path, + guest: s.from, + approver: session.login, + before, + after: s.body.replacement, + at: new Date().toISOString(), + }]); + suggestionsDone.set(id, "applied"); + }); + incoming.delete(id); + renderSide(); +} + +function declineSuggestion(id) { + if (session.role !== "member") return; + if (!suggestionsDone.has(id)) doc.transact(() => { suggestionsDone.set(id, "declined"); }); + incoming.delete(id); + renderSide(); +} +suggestionsDone.observe(() => { + for (const id of suggestionsDone.keys()) incoming.delete(id); + renderSide(); +}); + +// --------------------------------------------------------------------------- +// Step 4's hook: sending the session back to GitHub. meta.mode, the +// approvals array, the base snapshot and session.credits are what it reads. +// --------------------------------------------------------------------------- +function submitSession() { + toast(WORDS.submitComingNext); +} + +// --------------------------------------------------------------------------- +// The editor. +// --------------------------------------------------------------------------- +let view = null; +let currentPath = ""; +function openFile(path) { + if (view) { view.destroy(); view = null; } + clear(ui.editor); + awareness.setLocalStateField("cursor", null); + setAwarenessUser(); + const ytext = path ? files.get(path) : null; + if (!(ytext instanceof Y.Text)) { + currentPath = ""; + ui.filetitle.textContent = WORDS.noFile; + renderFiles(); + renderGuestBar(); + return; + } + currentPath = path; + ui.filetitle.textContent = path; + const extensions = [ + keymap.of([...yUndoManagerKeymap]), + basicSetup, + markdown(), + EditorView.lineWrapping, + yCollab(ytext, awareness), + ]; + if (session.role !== "member") { + // A guest reads and selects; the text changes only through a member. + extensions.push(EditorState.readOnly.of(true), EditorView.contentAttributes.of({ inputmode: "none" })); + extensions.push(EditorView.updateListener.of((u) => { if (u.selectionSet) renderGuestBar(); })); + } + view = new EditorView({ state: EditorState.create({ doc: ytext.toString(), extensions }), parent: ui.editor }); + renderFiles(); + renderGuestBar(); +} +files.observe(() => { + renderFiles(); + if (!currentPath || !files.has(currentPath)) { + const first = [...files.keys()].sort()[0]; + if (first && session.synced) openFile(first); + } +}); + +// --------------------------------------------------------------------------- +// Rendering. +// --------------------------------------------------------------------------- +const ui = {}; +let statusText = ""; +let statusBad = false; +function setStatus(text, bad) { + statusText = text; + statusBad = bad; + if (ui.status) { + ui.status.textContent = text; + ui.status.className = bad ? "status bad" : "status"; + } +} + +function build() { + const app = document.getElementById("app"); + clear(app); + ui.toast = el("div", { class: "toast", role: "status", "aria-live": "polite", hidden: true }); + if (!route) { + app.append(el("p", { class: "notice", text: WORDS.wrongAddress }), ui.toast); + return false; + } + document.title = route.slug + " · " + WORDS.title; + ui.status = el("span", { class: "status" }); + ui.who = el("span", { class: "who" }); + ui.signInTop = el("button", { type: "button", text: WORDS.signIn, onclick: () => startSignIn() }); + ui.submit = el("button", { type: "button", class: "primary", text: WORDS.submit, onclick: () => submitSession() }); + const bar = el("header", { class: "bar" }, + el("h1", { text: route.slug + " · " + WORDS.title }), ui.status, el("span", { class: "spacer" }), + ui.who, ui.signInTop, ui.submit); + + // The start screen. + ui.relay = el("input", { id: "relay", type: "url", inputmode: "url", autocomplete: "off", spellcheck: "false", placeholder: "https://" }); + ui.code = el("input", { id: "code", autocomplete: "off", spellcheck: "false", maxlength: "12", autocapitalize: "characters" }); + ui.name = el("input", { id: "name", autocomplete: "nickname", maxlength: "40" }); + ui.mode = el("select", { id: "mode" }, + el("option", { value: "pr", text: WORDS.modePR }), el("option", { value: "issues", text: WORDS.modeIssues })); + ui.token = el("input", { id: "token", type: "password", autocomplete: "off", spellcheck: "false" }); + ui.relayCard = el("section", { class: "card" }, + el("label", { for: "relay", text: WORDS.relayLabel }), ui.relay, el("p", { text: WORDS.relayHint })); + ui.joinCard = el("form", { class: "card", onsubmit: (e) => { e.preventDefault(); joinClicked(); } }, + el("h2", { text: WORDS.joinHeading }), el("p", { text: WORDS.joinHint }), + el("label", { for: "code", text: WORDS.codeLabel }), ui.code, + el("label", { for: "name", text: WORDS.nameLabel }), ui.name, + el("div", { class: "row" }, el("button", { type: "submit", class: "primary", text: WORDS.join }))); + ui.signedIn = el("p"); + ui.signInButton = el("button", { type: "button", class: "primary", text: WORDS.signIn, onclick: () => startSignIn() }); + ui.tokenToggle = el("button", { type: "button", text: WORDS.pasteToken, onclick: () => showTokenForm(true) }); + ui.tokenForm = el("form", { hidden: true, onsubmit: (e) => { e.preventDefault(); const v = ui.token.value; ui.token.value = ""; useToken(v); } }, + el("label", { for: "token", text: WORDS.tokenLabel }), ui.token, + el("div", { class: "row" }, el("button", { type: "submit", text: WORDS.useToken }))); + ui.hostRow = el("div", { hidden: true }, + el("h2", { text: WORDS.hostHeading }), + el("label", { for: "mode", text: WORDS.modeLabel }), ui.mode, + el("div", { class: "row" }, el("button", { type: "button", class: "primary", text: WORDS.host, onclick: () => hostClicked() }))); + ui.teamCard = el("section", { class: "card" }, + el("h2", { text: WORDS.teamHeading }), el("p", { text: WORDS.teamHint }), ui.signedIn, + el("div", { class: "row" }, ui.signInButton, ui.tokenToggle), ui.tokenForm, ui.hostRow); + ui.start = el("div", { class: "start" }, ui.relayCard, ui.joinCard, ui.teamCard); + + // The workspace. + ui.files = el("nav", { class: "files", "aria-label": WORDS.filesHeading }); + ui.filetitle = el("div", { class: "filetitle" }); + ui.editor = el("div", { class: "editor" }); + ui.quote = el("div", { class: "quote" }); + ui.replacement = el("textarea", { id: "replacement", maxlength: String(MAX_REPLACEMENT) }); + ui.suggestForm = el("form", { hidden: true, onsubmit: (e) => { e.preventDefault(); sendSuggestion(ui.replacement.value); ui.suggestForm.hidden = true; } }, + ui.quote, el("label", { for: "replacement", text: WORDS.replacementLabel }), ui.replacement, + el("div", { class: "row" }, + el("button", { type: "submit", class: "primary", text: WORDS.send }), + el("button", { type: "button", text: WORDS.cancel, onclick: () => { ui.suggestForm.hidden = true; } }))); + ui.suggestButton = el("button", { type: "button", text: WORDS.suggest, onclick: () => openSuggestForm() }); + ui.guestbar = el("div", { class: "guestbar", hidden: true }, + el("p", { class: "state", text: WORDS.guestHint }), el("div", { class: "row" }, ui.suggestButton), ui.suggestForm); + ui.side = el("aside", { class: "side" }); + ui.work = el("div", { class: "work", hidden: true }, + ui.files, el("section", { class: "center" }, ui.filetitle, ui.editor, ui.guestbar), ui.side); + + app.append(bar, ui.start, ui.work, ui.toast); + return true; +} + +function showTokenForm(show) { + ui.tokenForm.hidden = !show; + if (show) ui.token.focus(); +} + +function readRelayInput() { + if (session.inRoom || session.everJoined) return true; + const relay = relayOrigin(ui.relay.value); + if (!relay) { toast(WORDS.relayBad); ui.relay.focus(); return false; } + if (relay !== session.relay && ws) { const sock = ws; ws = null; sock.close(); } + session.relay = relay; + remember(KEY_RELAY, relay); + return true; +} + +function joinClicked() { + if (!readRelayInput()) return; + const code = normalizeCode(ui.code.value); + if (!code) { toast(WORDS.codeBad); ui.code.focus(); return; } + session.code = code; + session.name = ui.name.value.trim().slice(0, 40); + if (session.name) remember(KEY_NAME, session.name); + if (socketOpen()) proceed(); + else openSocket(); +} + +function hostClicked() { + if (!readRelayInput()) return; + session.code = ""; + if (socketOpen() && session.member) sendJSON({ type: "host", repo: route.slug }); + else { session.pending = "host"; openSocket(); } +} + +function openSuggestForm() { + if (!view) return; + const sel = view.state.selection.main; + if (sel.empty) { toast(WORDS.selectFirst); return; } + const text = view.state.sliceDoc(sel.from, sel.to); + ui.quote.textContent = text; + ui.replacement.value = text; + ui.suggestForm.hidden = false; + ui.replacement.focus(); +} + +function renderTop() { + if (!ui.who) return; + let who = ""; + if (session.login) who = WORDS.signedInAs + " " + session.login; + else if (session.inRoom && session.name) who = session.name; + ui.who.textContent = who; + ui.signInTop.hidden = !(session.inRoom && session.role === "guest" && !session.over); + ui.submit.hidden = !(session.synced && session.role === "member" && !session.over); + ui.status.textContent = statusText; + ui.status.className = statusBad ? "status bad" : "status"; +} + +function renderStart() { + if (!ui.start) return; + const inSession = session.everJoined || session.over; + ui.start.hidden = inSession; + ui.work.hidden = !inSession || session.seeding; + if (inSession) return; + ui.signedIn.textContent = session.login + ? WORDS.signedInAs + " " + session.login + (session.member ? "" : ". " + WORDS.notMember) + : ""; + ui.signInButton.hidden = !!session.login; + ui.tokenToggle.hidden = !!session.login; + ui.hostRow.hidden = !(session.login && session.member); +} + +function renderFiles() { + if (!ui.files) return; + clear(ui.files); + ui.files.append(el("h2", { text: WORDS.filesHeading })); + const paths = [...files.keys()].sort(); + let folder = null; + for (const p of paths) { + const rel = p.startsWith(BRIEF_PREFIX) ? p.slice(BRIEF_PREFIX.length) : p; + const cut = rel.lastIndexOf("/"); + const dir = cut < 0 ? "" : rel.slice(0, cut); + if (dir !== folder) { + folder = dir; + if (dir) ui.files.append(el("div", { class: "folder", text: dir + "/" })); + } + ui.files.append(el("button", { + type: "button", + text: cut < 0 ? rel : rel.slice(cut + 1), + "aria-current": p === currentPath ? "true" : "false", + onclick: () => openFile(p), + })); + } +} + +function renderGuestBar() { + if (!ui.guestbar) return; + const guest = session.role === "guest" && session.synced && !session.over && !!view; + ui.guestbar.hidden = !guest; + if (guest) ui.suggestButton.disabled = view.state.selection.main.empty; +} + +function badge(p) { + if (p.host) return el("span", { class: "badge host", text: WORDS.badgeHost }); + return el("span", { class: "badge", text: p.role === "member" ? WORDS.badgeMember : WORDS.badgeGuest }); +} + +function renderSide() { + if (!ui.side) return; + clear(ui.side); + if (session.host && session.code && !session.seeding && !session.over) { + const url = inviteURL(); + let img = null; + try { + const qr = qrcode(0, "M"); + qr.addData(url); + qr.make(); + img = el("img", { src: qr.createDataURL(6, 2), alt: WORDS.inviteHint }); + } catch { img = null; } + const mode = el("select", { id: "mode-live", onchange: (e) => { doc.transact(() => meta.set("mode", e.target.value)); } }, + el("option", { value: "pr", text: WORDS.modePR }), el("option", { value: "issues", text: WORDS.modeIssues })); + mode.value = meta.get("mode") === "issues" ? "issues" : "pr"; + ui.side.append(el("section", { class: "invite" }, + el("h2", { text: WORDS.inviteHeading }), img, + el("div", { class: "code", text: session.code, "aria-label": WORDS.codeLabel }), + el("p", { class: "state", text: WORDS.inviteHint }), + el("div", { class: "link", text: url }), + el("div", { class: "row" }, + el("button", { type: "button", text: WORDS.copyLink, onclick: () => copyLink(url) }), + el("button", { type: "button", text: WORDS.newCode, onclick: () => sendJSON({ type: "rotate" }) }), + el("button", { type: "button", text: WORDS.endSession, onclick: () => { if (confirm(WORDS.endConfirm)) sendJSON({ type: "close" }); } })), + el("label", { for: "mode-live", text: WORDS.modeLabel }), mode)); + } + + const list = el("ul"); + for (const p of session.roster) { + list.append(el("li", {}, p.name || p.login || "", badge(p), p.id === session.myId ? " " + WORDS.you : "")); + } + ui.side.append(el("section", { class: "roster" }, el("h2", { text: WORDS.rosterHeading }), list)); + + if (session.role === "member" && session.synced) { + const items = el("ul"); + for (const s of incoming.values()) { + if (suggestionsDone.has(s.id)) continue; + items.append(el("li", { class: "suggestion" }, + el("div", { class: "from", text: WORDS.suggestedBy + " " + s.from + " · " + s.body.path.replace(BRIEF_PREFIX, "") }), + el("del", { text: s.body.quote.exact }), + el("ins", { text: s.body.replacement }), + el("div", { class: "row" }, + el("button", { type: "button", class: "primary", text: WORDS.apply, onclick: () => applySuggestion(s.id) }), + el("button", { type: "button", text: WORDS.decline, onclick: () => declineSuggestion(s.id) })))); + } + ui.side.append(el("section", { class: "suggestions" }, el("h2", { text: WORDS.suggestionsHeading }), + items.childElementCount ? items : el("p", { class: "state", text: WORDS.noSuggestions }))); + } + + if (session.role === "guest" && mine.length) { + const items = el("ul"); + for (const s of mine) { + const done = suggestionsDone.get(s.id); + items.append(el("li", { class: "suggestion" }, + el("del", { text: s.body.quote.exact }), + el("ins", { text: s.body.replacement }), + el("div", { class: "state", text: done === "applied" ? WORDS.applied : done === "declined" ? WORDS.declined : WORDS.waiting }))); + } + ui.side.append(el("section", { class: "suggestions" }, el("h2", { text: WORDS.mySuggestionsHeading }), items)); + } +} + +async function copyLink(url) { + try { await navigator.clipboard.writeText(url); toast(WORDS.linkCopied); } catch { /* clipboard refused */ } +} + +function renderAll() { + renderTop(); + renderStart(); + renderSide(); + renderGuestBar(); + if (session.synced && !session.seeding && !view && files.size > 0) { + openFile([...files.keys()].sort()[0]); + } else if (view && session.over) { + view.destroy(); + view = null; + } +} + +// --------------------------------------------------------------------------- +// Start. +// --------------------------------------------------------------------------- +async function main() { + if (!build()) return; + const frag = new URLSearchParams(location.hash.slice(1)); + session.relay = relayOrigin(frag.get("relay")) || relayOrigin(recall(KEY_RELAY)); + session.code = normalizeCode(frag.get("code")); + session.name = recall(KEY_NAME); + ui.relay.value = session.relay; + ui.code.value = session.code; + ui.name.value = session.name; + if (frag.get("relay") && session.relay) ui.relayCard.hidden = true; + setStatus("", false); + renderAll(); + await finishSignIn(); + ui.relay.value = session.relay; + ui.code.value = session.code; + renderAll(); +} +main(); diff --git a/internal/surface/session/web/web.go b/internal/surface/session/web/web.go new file mode 100644 index 0000000000..8fa579006e --- /dev/null +++ b/internal/surface/session/web/web.go @@ -0,0 +1,186 @@ +// Package web is the brief session's page (itd-2610091016262335, +// spc-2610141709430146 step 3): the static files `abcd session page` writes, +// embedded in the binary. The page is plain HTML, one ES module and one +// stylesheet, plus the vendored, provenance-pinned editor bundle +// (adr-2610141949110653). It connects only to the relay the person names, to +// api.github.com with the person's own token, and to github.com for sign-in. +// +// The HTML is written twice, as index.html and as 404.html, byte for byte the +// same: a static host such as GitHub Pages serves 404.html for a path it has +// no file for, which is how ///brief/ reaches the page. Every +// asset the HTML loads is named relative to that depth (../../../), so the +// page works under a base path too. +package web + +import ( + "bytes" + "embed" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "sort" + + "github.com/intentdriven/abcd/internal/fsutil" +) + +//go:embed index.html session.js session.css vendor/collab.js vendor/LICENSES.txt +var assets embed.FS + +// Generator is the line the page's HTML carries, which is how a later run +// knows a directory holding the page's files as one it wrote. +const Generator = `` + +// File is one file the page is made of. +type File struct { + Path string // slash-separated, relative to the output directory + Data []byte +} + +// sources maps each written path to the embedded asset it is a copy of. +var sources = []struct{ path, asset string }{ + {"index.html", "index.html"}, + {"404.html", "index.html"}, + {"session.js", "session.js"}, + {"session.css", "session.css"}, + {"vendor/collab.js", "vendor/collab.js"}, + {"vendor/LICENSES.txt", "vendor/LICENSES.txt"}, +} + +// Files is every file the page is made of, in the order they are written. +func Files() []File { + out := make([]File, 0, len(sources)) + for _, s := range sources { + data, err := assets.ReadFile(s.asset) + if err != nil { + // The embed directive names every asset; a missing one is a + // build that cannot happen. + panic(err) + } + out = append(out, File{Path: s.path, Data: data}) + } + return out +} + +// Result is what Write did. +type Result struct { + Out string `json:"out"` + Files []string `json:"files"` + Bytes int64 `json:"bytes"` + // Replaced is true when the directory held an earlier copy of the page. + Replaced bool `json:"replaced"` +} + +// Refusal is a directory Write will not write into; nothing was written. +type Refusal struct{ Reason string } + +func (r *Refusal) Error() string { return r.Reason } + +// Write writes the page's files into out and nothing else. out may be absent +// or empty; a directory holding anything but the page's own files, or holding +// them without the page's own index.html, is refused untouched. Nothing is +// ever removed: an earlier copy of the page is overwritten file by file. +func Write(out string) (Result, error) { + if out == "" { + return Result{}, &Refusal{Reason: "name the directory to write the page into with --out"} + } + abs, err := filepath.Abs(out) + if err != nil { + return Result{}, err + } + replaced, err := inspect(abs) + if err != nil { + return Result{}, err + } + if err := os.MkdirAll(abs, 0o755); err != nil { + return Result{}, err + } + if !fsutil.IsRealDir(abs) { + return Result{}, &Refusal{Reason: abs + " is not a real directory; the page is written only into one"} + } + root, err := os.OpenRoot(abs) + if err != nil { + return Result{}, err + } + defer root.Close() + // Asked again through the handle the writes go through, so a directory + // filled between the first look and the first write is still refused. + if _, err := inspectRoot(root, abs); err != nil { + return Result{}, err + } + res := Result{Out: abs, Replaced: replaced} + for _, f := range Files() { + if err := fsutil.WriteFileAtomicInRoot(root, f.Path, f.Data, 0o644); err != nil { + return res, err + } + res.Files = append(res.Files, f.Path) + res.Bytes += int64(len(f.Data)) + } + return res, nil +} + +// inspect judges out before anything is created: absent or empty is fine, a +// symlink or a file is refused, and a directory is judged by inspectRoot. +func inspect(abs string) (bool, error) { + fi, err := os.Lstat(abs) + switch { + case errors.Is(err, fs.ErrNotExist): + return false, nil + case err != nil: + return false, err + case fi.Mode()&os.ModeSymlink != 0: + return false, &Refusal{Reason: abs + " is a symlink; the page is written only into a real directory"} + case !fi.IsDir(): + return false, &Refusal{Reason: abs + " is not a directory"} + } + root, err := os.OpenRoot(abs) + if err != nil { + return false, err + } + defer root.Close() + return inspectRoot(root, abs) +} + +// inspectRoot reports whether the directory holds an earlier copy of the +// page, and refuses one holding anything else. +func inspectRoot(root *os.Root, abs string) (bool, error) { + ours := map[string]bool{"vendor": true} + for _, s := range sources { + ours[s.path] = true + } + var found []string + foreign := "" + err := fs.WalkDir(root.FS(), ".", func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if p == "." { + return nil + } + want := ours[p] + isDir := p == "vendor" + switch { + case !want, d.Type()&fs.ModeSymlink != 0, d.IsDir() != isDir, !d.IsDir() && !d.Type().IsRegular(): + foreign = p + return fs.SkipAll + } + found = append(found, p) + return nil + }) + if err != nil { + return false, err + } + if foreign != "" { + return false, &Refusal{Reason: fmt.Sprintf("%s holds %s, which the session page does not write; point --out at an empty directory or one an earlier `abcd session page` wrote", abs, foreign)} + } + if len(found) == 0 { + return false, nil + } + head, err := fsutil.ReadGuardedInRoot(root, "index.html", 64<<10) + if err != nil || !bytes.Contains(head, []byte(Generator)) { + sort.Strings(found) + return false, &Refusal{Reason: fmt.Sprintf("%s holds %s but no index.html the session page wrote, so it cannot be told apart from a directory it did not write", abs, found[0])} + } + return true, nil +} From d0c63e01561b0583fa7d7c9e8ec74a35e8fbdcf2 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:19:48 +0100 Subject: [PATCH 025/165] test: pin the session page's files, provenance and safety The page writes exactly its six files, byte-identical to the embedded assets, and refuses a foreign or symlinked directory untouched. The vendored bundle and its entry module match PROVENANCE.json, and the check is shown failing on a tampered copy. The HTML carries the policy and loads only its own files; the script holds no innerHTML or eval, imports only the bundle, names only GitHub's API and sign-in page, and keeps the token out of storage, URLs, the page and logs. Assisted-by: Claude:claude-opus-5-5 --- internal/surface/session/web/web_test.go | 393 +++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 internal/surface/session/web/web_test.go diff --git a/internal/surface/session/web/web_test.go b/internal/surface/session/web/web_test.go new file mode 100644 index 0000000000..51c9211bde --- /dev/null +++ b/internal/surface/session/web/web_test.go @@ -0,0 +1,393 @@ +package web + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "testing" +) + +// The files the page is made of, and nothing else. +var wantFiles = []string{"404.html", "index.html", "session.css", "session.js", "vendor/LICENSES.txt", "vendor/collab.js"} + +// listTree lists every entry under dir, slash-separated, directories marked +// with a trailing slash. +func listTree(t *testing.T, dir string) []string { + t.Helper() + var got []string + err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + rel, _ := filepath.Rel(dir, p) + if rel == "." { + return nil + } + rel = filepath.ToSlash(rel) + if d.IsDir() { + rel += "/" + } + got = append(got, rel) + return nil + }) + if err != nil { + t.Fatal(err) + } + sort.Strings(got) + return got +} + +func TestPageWritesExactlyItsFilesAsEmbedded(t *testing.T) { + out := filepath.Join(t.TempDir(), "page") + res, err := Write(out) + if err != nil { + t.Fatal(err) + } + want := append([]string{"vendor/"}, wantFiles...) + sort.Strings(want) + if got := listTree(t, out); strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("wrote %v, want %v", got, want) + } + if res.Replaced || len(res.Files) != len(wantFiles) { + t.Fatalf("result = %+v", res) + } + var total int64 + for _, f := range Files() { + got, err := os.ReadFile(filepath.Join(out, filepath.FromSlash(f.Path))) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, f.Data) { + t.Errorf("%s differs from the embedded asset", f.Path) + } + // Every embedded asset is the committed file beside this test. + src := f.Path + if src == "404.html" { + src = "index.html" + } + disk, err := os.ReadFile(filepath.FromSlash(src)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, disk) { + t.Errorf("%s differs from the committed %s", f.Path, src) + } + total += int64(len(got)) + } + if res.Bytes != total { + t.Errorf("reported %d bytes, wrote %d", res.Bytes, total) + } + + // A second run replaces its own copy, and still writes nothing else. + res, err = Write(out) + if err != nil || !res.Replaced { + t.Fatalf("rewrite: %+v, %v", res, err) + } + if got := listTree(t, out); strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("rewrite left %v", got) + } +} + +func Test404IsTheIndex(t *testing.T) { + byPath := map[string][]byte{} + for _, f := range Files() { + byPath[f.Path] = f.Data + } + if !bytes.Equal(byPath["404.html"], byPath["index.html"]) || len(byPath["index.html"]) == 0 { + t.Fatal("404.html is not index.html byte for byte") + } +} + +// refused asserts that Write refused out and left it exactly as it was. +func refused(t *testing.T, out string) { + t.Helper() + before := listTree(t, out) + _, err := Write(out) + var ref *Refusal + if !errors.As(err, &ref) { + t.Fatalf("Write(%s) = %v, want a refusal", out, err) + } + if after := listTree(t, out); strings.Join(after, ",") != strings.Join(before, ",") { + t.Fatalf("a refusal changed the directory: %v -> %v", before, after) + } +} + +func TestPageRefusesADirectoryItDidNotWrite(t *testing.T) { + t.Run("a foreign file", func(t *testing.T) { + out := t.TempDir() + if err := os.WriteFile(filepath.Join(out, "notes.txt"), []byte("mine\n"), 0o644); err != nil { + t.Fatal(err) + } + refused(t, out) + }) + t.Run("a foreign file beside an earlier copy", func(t *testing.T) { + out := t.TempDir() + if _, err := Write(out); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(out, "vendor", "extra.js"), []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + refused(t, out) + }) + t.Run("the page's names without its index", func(t *testing.T) { + out := t.TempDir() + if err := os.WriteFile(filepath.Join(out, "session.js"), []byte("someone else's\n"), 0o644); err != nil { + t.Fatal(err) + } + refused(t, out) + }) + t.Run("an index.html the page did not write", func(t *testing.T) { + out := t.TempDir() + if err := os.WriteFile(filepath.Join(out, "index.html"), []byte("mine\n"), 0o644); err != nil { + t.Fatal(err) + } + refused(t, out) + }) + t.Run("a symlink inside", func(t *testing.T) { + out := t.TempDir() + if _, err := Write(out); err != nil { + t.Fatal(err) + } + if err := os.RemoveAll(filepath.Join(out, "vendor")); err != nil { + t.Fatal(err) + } + if err := os.Symlink(t.TempDir(), filepath.Join(out, "vendor")); err != nil { + t.Fatal(err) + } + refused(t, out) + }) + t.Run("a symlink or a file as --out", func(t *testing.T) { + dir := t.TempDir() + link := filepath.Join(dir, "link") + if err := os.Symlink(t.TempDir(), link); err != nil { + t.Fatal(err) + } + if _, err := Write(link); !errors.As(err, new(*Refusal)) { + t.Fatalf("symlink --out: %v", err) + } + file := filepath.Join(dir, "file") + if err := os.WriteFile(file, nil, 0o644); err != nil { + t.Fatal(err) + } + if _, err := Write(file); !errors.As(err, new(*Refusal)) { + t.Fatalf("file --out: %v", err) + } + }) + t.Run("no --out", func(t *testing.T) { + if _, err := Write(""); !errors.As(err, new(*Refusal)) { + t.Fatalf("empty --out: %v", err) + } + }) +} + +// provenance is the part of PROVENANCE.json the hash check reads. +type provenance struct { + Bundle string `json:"bundle"` + SHA256 string `json:"sha256"` + Entry string `json:"entry"` + EntrySHA256 string `json:"entry_sha256"` + Packages []struct { + Name, Version, License, Integrity, Resolved string + } `json:"packages"` +} + +// checkProvenance proves the bundle and its entry module in dir are the bytes +// PROVENANCE.json records (adr-2610141949110653). +func checkProvenance(dir string) error { + raw, err := os.ReadFile(filepath.Join(dir, "PROVENANCE.json")) + if err != nil { + return err + } + var p provenance + if err := json.Unmarshal(raw, &p); err != nil { + return err + } + if p.Bundle != "collab.js" || p.Entry != "collab.entry.js" || len(p.Packages) == 0 { + return fmt.Errorf("PROVENANCE.json names %q and %q with %d packages", p.Bundle, p.Entry, len(p.Packages)) + } + for _, c := range []struct{ file, want string }{{p.Bundle, p.SHA256}, {p.Entry, p.EntrySHA256}} { + data, err := os.ReadFile(filepath.Join(dir, c.file)) + if err != nil { + return err + } + sum := sha256.Sum256(data) + if got := hex.EncodeToString(sum[:]); got != c.want { + return fmt.Errorf("%s has sha256 %s, PROVENANCE.json records %s", c.file, got, c.want) + } + } + for _, pkg := range p.Packages { + if pkg.License != "MIT" || !strings.HasPrefix(pkg.Integrity, "sha512-") || !strings.HasPrefix(pkg.Resolved, "https://registry.npmjs.org/") { + return fmt.Errorf("package %s@%s: licence %q, integrity %q, resolved %q", pkg.Name, pkg.Version, pkg.License, pkg.Integrity, pkg.Resolved) + } + } + return nil +} + +func TestVendoredBundleMatchesItsProvenance(t *testing.T) { + if err := checkProvenance("vendor"); err != nil { + t.Fatal(err) + } + embedded, err := assets.ReadFile("vendor/collab.js") + if err != nil { + t.Fatal(err) + } + disk, err := os.ReadFile(filepath.Join("vendor", "collab.js")) + if err != nil || !bytes.Equal(embedded, disk) { + t.Fatalf("the embedded collab.js is not the committed one: %v", err) + } +} + +func TestProvenanceCheckCatchesATamperedBundle(t *testing.T) { + for _, name := range []string{"collab.js", "collab.entry.js"} { + t.Run(name, func(t *testing.T) { + dir := t.TempDir() + for _, f := range []string{"PROVENANCE.json", "collab.js", "collab.entry.js"} { + data, err := os.ReadFile(filepath.Join("vendor", f)) + if err != nil { + t.Fatal(err) + } + if f == name { + data = append(data, []byte("\n// tampered\n")...) + } + if err := os.WriteFile(filepath.Join(dir, f), data, 0o644); err != nil { + t.Fatal(err) + } + } + err := checkProvenance(dir) + if err == nil || !strings.Contains(err.Error(), name+" has sha256") { + t.Fatalf("a tampered %s passed the provenance check: %v", name, err) + } + }) + } +} + +// cspWant is the page's content-security policy, in full. +const cspWant = `default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src https: wss: http://localhost:* ws://localhost:* http://127.0.0.1:* ws://127.0.0.1:*; base-uri 'none'; form-action 'none'` + +func TestPageHTMLLoadsOnlyItsOwnFiles(t *testing.T) { + html, err := assets.ReadFile("index.html") + if err != nil { + t.Fatal(err) + } + s := string(html) + for _, want := range []string{ + ``, + ``, + Generator, + ``, + ``, + } { + if strings.Count(s, want) != 1 { + t.Errorf("index.html does not carry %s exactly once", want) + } + } + // Every src and href: the page's own files, named relative to the + // ///brief/ depth so a base path works, or an empty icon. + allowed := map[string]bool{ + "../../../session.js": true, "../../../session.css": true, "../../../vendor/collab.js": true, "data:,": true, + } + refs := regexp.MustCompile(`(?i)\b(?:src|href)\s*=\s*"([^"]*)"`).FindAllStringSubmatch(s, -1) + if len(refs) != 4 { + t.Errorf("index.html has %d src/href attributes, want 4: %v", len(refs), refs) + } + for _, m := range refs { + if !allowed[m[1]] { + t.Errorf("index.html loads %q", m[1]) + } + } + if n := strings.Count(strings.ToLower(s), " Date: Wed, 14 Oct 2026 22:19:55 +0100 Subject: [PATCH 026/165] feat: wire abcd session page `abcd session page --out ` writes the brief session's static page from the assets the binary embeds, and nothing else; a directory holding anything it did not write is refused with exit 2 and nothing written. The sub-verb gets its sentence, surface snapshot, CLI reference and generated appendix, and the plugin page and brief surface chapter describe the page: joining by fragment, sign-in, hosting and seeding, editing and credit, guests' suggestions and approvals. Assisted-by: Claude:claude-opus-5-5 --- .../brief/04-surfaces/37-session.md | 77 +++++++++++++++++-- .abcd/development/brief/04-surfaces/README.md | 2 +- .abcd/development/release/surface.json | 16 +++- commands/session.md | 35 ++++++++- docs/reference/cli/commands.md | 33 +++++++- internal/core/surface/sentences.go | 4 +- internal/surface/cli/barerender.go | 5 +- internal/surface/cli/session.go | 54 ++++++++++++- internal/surface/cli/session_surface_test.go | 36 +++++++++ 9 files changed, 239 insertions(+), 23 deletions(-) diff --git a/.abcd/development/brief/04-surfaces/37-session.md b/.abcd/development/brief/04-surfaces/37-session.md index e26492f0b2..0455d20318 100644 --- a/.abcd/development/brief/04-surfaces/37-session.md +++ b/.abcd/development/brief/04-surfaces/37-session.md @@ -1,8 +1,9 @@ # `/abcd:session` — The Brief Session's Relay -`/abcd:session` runs the relay a team's live brief session goes through, and -writes the blueprint that deploys it as the team's own relay -(itd-2610091016262335, spc-2610141709430146 steps 1 and 2). abcd runs no relay +`/abcd:session` runs the relay a team's live brief session goes through, +writes the blueprint that deploys it as the team's own relay, and writes the +static page the session is held on (itd-2610091016262335, +spc-2610141709430146 steps 1 to 3). abcd runs no relay and holds no key ([adr-2610091016270711](../../decisions/adrs/2610091016270711-who-may-change-an-abcd-managed-project-s.md)): the team runs it, on the host's own machine or on the team's own Render @@ -11,8 +12,8 @@ account. It is abcd's second listener, admitted on the terms of amendment of 2026-10-14 and brief invariant 7's inbound clause ([`02-constraints/03-invariants.md`](../02-constraints/03-invariants.md)). -This chapter describes the relay and its blueprint. The session page that -connects to it, and the route back to GitHub, are the spec's later steps. +This chapter describes the relay, its blueprint and the page. The route back +to GitHub is the spec's later step. ## Sub-verbs @@ -28,6 +29,7 @@ connects to it, and the route back to GitHub, are the spec's later steps. |---|---|---| | `serve` | — | shipped | | `blueprint` | — | shipped | +| `page` | — | shipped | ## Where the relay listens @@ -101,9 +103,62 @@ that was edited or that abcd did not write. It refuses outside a checkout abcd manages and for a release that publishes no checksum for the linux/amd64 binary. +## The page + +The page is written into a directory the person names, from files embedded in +the binary: `index.html` and an identical `404.html`, `session.js`, +`session.css`, and the vendored editor bundle `vendor/collab.js` with its +licences +([adr-2610141949110653](../../decisions/adrs/2610141949110653-the-brief-session-s-browser-editor-ships.md)). +It writes those six files and nothing else and removes nothing. The directory +may be absent, empty, or one an earlier run wrote, whose files are replaced; +one holding anything else, or the page's names without the `index.html` it +wrote, is refused with nothing written. The bundle's bytes and its entry +module are checked against the committed provenance by a test. + +A static host's 404 fallback serves the page at `///brief/`; the +HTML names its own files three levels up, so a base path works too. Its +content-security policy loads scripts and styles from the page's own origin +only and lets it connect over https and wss, or to loopback for a local +relay; it sends no referrer, and every piece of brief, relay or participant +text is put on the page as text, never as markup. + +- **Joining.** A joiner's relay address and code arrive in the URL fragment of + the host's QR code, so neither reaches the static host; a host types the + relay address once and the page remembers it. Only an https relay is + accepted, or plain http on this computer. +- **Signing in.** "Sign in with GitHub" goes to GitHub's authorize page with + PKCE and a random state, both held in session storage across the redirect; + the page posts the returned code and verifier to the relay's `/auth/token`. + Pasting a personal access token is the fallback. The token is held in the + page's memory only, and is presented to the relay as `verify`. +- **Hosting.** A team member hosts; the page reads the default branch's brief + through GitHub's API with the host's token and seeds one shared document + with a text per file, once, recording the base commit, the branch, the + route back (one pull request by default, or one issue per change) and a + snapshot of the seeded state. The seed is sent before the invite shows: a + QR code of the page's address with the relay and code in the fragment, the + code in large letters, and buttons for a new code and to end the session. +- **Editing.** A file list grouped by folder, and a Markdown editor bound to + the shared text, with each participant's cursor and name. A team member's + edits are sent as they are typed and the page registers its client id for + credit; edits made while the connection is down are sent when it returns, + and the page rejoins with the same code and replays the log. +- **Guests.** A guest's editor is read-only. A guest selects a passage and + proposes a replacement, sent with the passage's relative positions and a + text quote of it. Team members see it in a suggestions panel: Apply replaces + the passage, found by its positions or else by its quote and refused when + neither still matches, and records the guest, the approving member, the text + before and after, and the time, in one change; Decline drops it, and both + are recorded so every member's panel agrees. + +Sending the session back to GitHub is the next step; the page shows that it +is coming. + ## Exit codes -`0` done; `1` a failure after the relay started; `2` refused, with nothing +`0` done; `1` a failure after the relay started, or while the page was being +written; `2` refused, with nothing started or written. @@ -114,7 +169,7 @@ _Generated from the command tree; a drift test fails `go test` when this appendi ### `abcd session` -Sub-verbs: `abcd session blueprint`, `abcd session serve`. +Sub-verbs: `abcd session blueprint`, `abcd session page`, `abcd session serve`. Flags: none. @@ -126,6 +181,14 @@ Sub-verbs: none. |---|---| | `--release` | string | +### `abcd session page` + +Sub-verbs: none. + +| Flag | Type | +|---|---| +| `--out` | string | + ### `abcd session serve` Sub-verbs: none. diff --git a/.abcd/development/brief/04-surfaces/README.md b/.abcd/development/brief/04-surfaces/README.md index 36cfcc44a8..fa560b1be5 100644 --- a/.abcd/development/brief/04-surfaces/README.md +++ b/.abcd/development/brief/04-surfaces/README.md @@ -44,7 +44,7 @@ are wiring rather than user-facing surface are listed separately under | 34 | `/abcd:build` | shipped | Start the loop that takes one READY intent to delivered, refusing while a question is open or a peer holds it | [`34-build.md`](34-build.md) | | 35 | `/abcd:drain` | shipped | Work the open ledger unattended: preview with `--dry-run` which open issues a machine may fix alone and in what order, then run it one lane at a time, routing every hand-back by its kind | [`35-drain.md`](35-drain.md) | | 36 | `/abcd:dashboard` | shipped | Open the project from a phone or another computer on your own Tailscale network, through a server that answers only the devices Tailscale names | [`36-dashboard.md`](36-dashboard.md) | -| 37 | `/abcd:session` | shipped | Run the relay a team's live brief session goes through, on the host's machine over Tailscale Serve or on the team's own relay, and write that relay's blueprint | [`37-session.md`](37-session.md) | +| 37 | `/abcd:session` | shipped | Run the relay a team's live brief session goes through, on the host's machine over Tailscale Serve or on the team's own relay, write that relay's blueprint, and write the session's static page | [`37-session.md`](37-session.md) | ## How much of this table a machine keeps honest diff --git a/.abcd/development/release/surface.json b/.abcd/development/release/surface.json index 221f8430aa..2dda19da08 100644 --- a/.abcd/development/release/surface.json +++ b/.abcd/development/release/surface.json @@ -3311,7 +3311,7 @@ "hidden": false, "group": "set-up", "block": "people", - "sentence": "Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb.", + "sentence": "Run the relay a team's live brief session goes through, or write its blueprint or its page: Writes nothing bare; refuses an unknown sub-verb.", "flags": [] }, { @@ -3328,6 +3328,20 @@ } ] }, + { + "path": "abcd session page", + "hidden": false, + "sentence": "Write the brief session's static page and its editor bundle into a directory: Writes only the page's six files; refuses a directory holding anything else.", + "flags": [ + { + "name": "out", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + } + ] + }, { "path": "abcd session serve", "hidden": false, diff --git a/commands/session.md b/commands/session.md index 859bea0029..6bb1355c70 100644 --- a/commands/session.md +++ b/commands/session.md @@ -1,7 +1,7 @@ --- name: session -description: "Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb." -argument-hint: "[serve|blueprint]" +description: "Run the relay a team's live brief session goes through, or write its blueprint or its page: Writes nothing bare; refuses an unknown sub-verb." +argument-hint: "[serve|blueprint|page]" block: people --- @@ -16,7 +16,10 @@ when the host closes the session or nobody has been connected for half an hour. It writes nothing to disk and logs nothing: no token, no secret, no brief text. -There are two ways to run it, both the team's own: +The page itself is a set of static files `page` writes, for the team to serve +from any static host. + +There are two ways to run the relay, both the team's own: - **On the host's own machine**, with `serve`, reached by teammates on the host's Tailscale network through Tailscale Serve. Never Funnel: a guest @@ -27,7 +30,7 @@ There are two ways to run it, both the team's own: ## Status -Bare invocation prints the two sub-verbs and writes nothing. A running relay's +Bare invocation prints the three sub-verbs and writes nothing. A running relay's sessions live in that process's memory alone, so there is no state to report. ```bash @@ -88,6 +91,30 @@ repository, sets the Blueprint path to `.abcd/relay/render.yaml`, and sets service's Environment. `--json` returns `release`, `asset`, `sha256`, `checksums_url` and `files`, each with its `path` and `state`. +## Page + +```bash +"${CLAUDE_PLUGIN_ROOT}/abcd" session page --out +``` + +It writes the session page into ``: `index.html` and an identical +`404.html`, `session.js`, `session.css`, and `vendor/collab.js` with +`vendor/LICENSES.txt`, the vendored editor bundle and its licences. The files +are embedded in the binary and read nothing from the checkout. A static host +whose 404 fallback serves `404.html`, such as GitHub Pages, is how +`///brief/` reaches the page; the page loads its own files from +three levels up, so it also works under a base path. The page connects only to +the relay the person names, to `api.github.com` with the person's own token, +and to `github.com` for sign-in. The directory is the one the user names; +never publish or deploy it for them. + +It writes those six files and nothing else, and removes nothing. `` may be +absent, empty, or a directory an earlier run wrote, whose files are replaced. +It refuses (exit 2, nothing written) a missing `--out`, a symlink or a file, +and a directory holding anything else or holding the page's names without the +`index.html` it wrote. Relay the files it wrote and the next step it prints. +`--json` returns `out`, `files`, `bytes` and `replaced`. + **Binary resolution.** Run `"${CLAUDE_PLUGIN_ROOT}/abcd"` — a plugin install provisions the binary into the plugin root, so this is the rung that fires for a plugin user. If that path does not exist, try `abcd` on `PATH`; if that fails diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md index 37eb66ebf0..2f2ff47227 100644 --- a/docs/reference/cli/commands.md +++ b/docs/reference/cli/commands.md @@ -3714,12 +3714,13 @@ abcd scribe ingest --scribe-json scribe.json --dispositions dispositions.md ### `abcd session` -Run the relay a team's live brief session goes through, or write its deployment blueprint: Writes nothing bare; refuses an unknown sub-verb. +Run the relay a team's live brief session goes through, or write its blueprint or its page: Writes nothing bare; refuses an unknown sub-verb. **Usage:** `abcd session` -Run the relay a team's live brief session goes through, or write the blueprint -that deploys it to the team's own Render account. The relay holds each session's +Run the relay a team's live brief session goes through, write the blueprint +that deploys it to the team's own Render account, or write the session's static +page for the team to serve. The relay holds each session's edits in memory only, as opaque bytes, and drops them when the host closes the session or nobody has been connected for half an hour; it writes nothing to disk and logs nothing. abcd runs no relay of its own: the team runs it, on the host's @@ -3752,6 +3753,32 @@ Exit 2 on a refusal, with nothing written. --release string the abcd release tag to pin, as vMAJOR.MINOR.PATCH (default: this abcd's own version, when it is a release) ``` +#### `abcd session page` + +Write the brief session's static page and its editor bundle into a directory: Writes only the page's six files; refuses a directory holding anything else. + +**Usage:** `abcd session page [flags]` + +Write the brief session's page into --out: index.html and an identical 404.html, +session.js, session.css, and the vendored editor bundle with its licences under +vendor/. The files are plain and static, embedded in this binary; serve them +from any static host, such as GitHub Pages, whose 404.html fallback is how +///brief/ reaches the page. The page connects only to the relay the +person names, to api.github.com with their own token, and to github.com for +sign-in. It reads nothing from this checkout. + +It writes those six files and nothing else, and removes nothing. --out may be +absent, empty, or a directory an earlier run wrote, whose files are replaced; a +directory holding anything else, a symlink or a file is refused. + +Exit 2 on a refusal, with nothing written. + +**Flags:** + +``` + --out string the directory to write the page into: absent, empty, or one an earlier run wrote +``` + #### `abcd session serve` Run the brief session relay in the foreground, on loopback by default: Writes nothing; refuses a --listen that is not an IP address and port. diff --git a/internal/core/surface/sentences.go b/internal/core/surface/sentences.go index 9b2903d602..1a0b07c7d5 100644 --- a/internal/core/surface/sentences.go +++ b/internal/core/surface/sentences.go @@ -228,10 +228,12 @@ var sentences = map[string]string{ "abcd dashboard stop": "Stop the dashboard after checking its process is the one start launched: " + "Writes the run file's removal; refuses when another start or stop holds the lock.", - "abcd session": "Run the relay a team's live brief session goes through, or write its deployment blueprint: " + + "abcd session": "Run the relay a team's live brief session goes through, or write its blueprint or its page: " + "Writes nothing bare; refuses an unknown sub-verb.", "abcd session blueprint": "Write the team relay's Render blueprint and Dockerfile, pinned to a release's checksum: " + "Writes .abcd/relay/; refuses an edited file.", + "abcd session page": "Write the brief session's static page and its editor bundle into a directory: " + + "Writes only the page's six files; refuses a directory holding anything else.", "abcd session serve": "Run the brief session relay in the foreground, on loopback by default: " + "Writes nothing; refuses a --listen that is not an IP address and port.", diff --git a/internal/surface/cli/barerender.go b/internal/surface/cli/barerender.go index 7027fe3e09..b372f2eaae 100644 --- a/internal/surface/cli/barerender.go +++ b/internal/surface/cli/barerender.go @@ -43,8 +43,9 @@ var bareRenderExceptions = map[string]string{ "a terminal and refuses (exit 2) anywhere else", "scribe": "a parent whose sub-verbs act on a named reading run or on a scribe's " + "returned output; with no operand it has no state of its own to render, so bare prints its sub-verbs", - "session": "a parent of serve, which runs the relay in the foreground, and blueprint, " + - "which writes the team relay's files; a running relay's sessions live in that " + + "session": "a parent of serve, which runs the relay in the foreground, blueprint, " + + "which writes the team relay's files, and page, which writes the session's static " + + "page; a running relay's sessions live in that " + "process's memory alone, so there is no state to render and bare prints its sub-verbs", "statusline": "its row belongs to a managed repository: there bare renders it with or " + "without the payload (TestStatuslineEmptyStdinStillRendersTheBadge), and anywhere else, " + diff --git a/internal/surface/cli/session.go b/internal/surface/cli/session.go index 00c2672652..67626aa078 100644 --- a/internal/surface/cli/session.go +++ b/internal/surface/cli/session.go @@ -14,6 +14,7 @@ import ( "github.com/intentdriven/abcd/internal/core/update" "github.com/intentdriven/abcd/internal/gitutil" "github.com/intentdriven/abcd/internal/surface/session" + "github.com/intentdriven/abcd/internal/surface/session/web" "github.com/intentdriven/abcd/internal/termsafe" "github.com/spf13/cobra" ) @@ -36,12 +37,14 @@ var newSessionReleaseFetcher = func() (coresession.AssetFetcher, error) { } // newSessionCommand builds the `session` verb, the front door onto the brief -// session's relay (itd-2610091016262335, spc-2610141709430146 steps 1 and 2). +// session's relay and page (itd-2610091016262335, spc-2610141709430146 steps +// 1 to 3). func newSessionCommand(asJSON *bool) *cobra.Command { cmd := &cobra.Command{ Use: "session", - Long: "Run the relay a team's live brief session goes through, or write the blueprint\n" + - "that deploys it to the team's own Render account. The relay holds each session's\n" + + Long: "Run the relay a team's live brief session goes through, write the blueprint\n" + + "that deploys it to the team's own Render account, or write the session's static\n" + + "page for the team to serve. The relay holds each session's\n" + "edits in memory only, as opaque bytes, and drops them when the host closes the\n" + "session or nobody has been connected for half an hour; it writes nothing to disk\n" + "and logs nothing. abcd runs no relay of its own: the team runs it, on the host's\n" + @@ -50,7 +53,7 @@ func newSessionCommand(asJSON *bool) *cobra.Command { Args: cobra.NoArgs, RunE: helpRunE, } - cmd.AddCommand(newSessionServeCommand(asJSON), newSessionBlueprintCommand(asJSON)) + cmd.AddCommand(newSessionServeCommand(asJSON), newSessionBlueprintCommand(asJSON), newSessionPageCommand(asJSON)) return cmd } @@ -215,3 +218,46 @@ func newSessionBlueprintCommand(asJSON *bool) *cobra.Command { c.Flags().StringVar(&release, "release", "", "the abcd release tag to pin, as vMAJOR.MINOR.PATCH (default: this abcd's own version, when it is a release)") return c } + +func newSessionPageCommand(asJSON *bool) *cobra.Command { + var out string + c := &cobra.Command{ + Use: "page", + Long: "Write the brief session's page into --out: index.html and an identical 404.html,\n" + + "session.js, session.css, and the vendored editor bundle with its licences under\n" + + "vendor/. The files are plain and static, embedded in this binary; serve them\n" + + "from any static host, such as GitHub Pages, whose 404.html fallback is how\n" + + "///brief/ reaches the page. The page connects only to the relay the\n" + + "person names, to api.github.com with their own token, and to github.com for\n" + + "sign-in. It reads nothing from this checkout.\n\n" + + "It writes those six files and nothing else, and removes nothing. --out may be\n" + + "absent, empty, or a directory an earlier run wrote, whose files are replaced; a\n" + + "directory holding anything else, a symlink or a file is refused.\n\n" + + "Exit 2 on a refusal, with nothing written.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + const prefix = "abcd session page: " + res, err := web.Write(out) + if err != nil { + var ref *web.Refusal + if errors.As(err, &ref) { + return &exitError{Code: 2, Msg: prefix + ref.Error() + " (nothing written)"} + } + return fmt.Errorf("%s%w", prefix, err) + } + return render(cmd.OutOrStdout(), *asJSON, res, func(w io.Writer) { + verb := "wrote" + if res.Replaced { + verb = "replaced" + } + fmt.Fprintf(w, "abcd session page: %s %d files (%d bytes) in %s\n", verb, len(res.Files), res.Bytes, termsafe.Sanitize(res.Out)) + for _, f := range res.Files { + fmt.Fprintf(w, " %s\n", f) + } + fmt.Fprintln(w, " next: serve the directory from a static host; the page opens at ///brief/") + }) + }, + } + c.Flags().StringVar(&out, "out", "", "the directory to write the page into: absent, empty, or one an earlier run wrote") + return c +} diff --git a/internal/surface/cli/session_surface_test.go b/internal/surface/cli/session_surface_test.go index 1245f2a8da..27a171cf2e 100644 --- a/internal/surface/cli/session_surface_test.go +++ b/internal/surface/cli/session_surface_test.go @@ -256,3 +256,39 @@ func TestSessionServeRefusals(t *testing.T) { } } } + +func TestSessionPageWritesThePageAndRefusesAForeignDirectory(t *testing.T) { + out := filepath.Join(t.TempDir(), "page") + text := string(runCLI(t, "session", "page", "--out", out)) + for _, want := range []string{"wrote 6 files", "index.html", "404.html", "vendor/collab.js", "///brief/"} { + if !strings.Contains(text, want) { + t.Errorf("output does not say %q:\n%s", want, text) + } + } + var res struct { + Out string `json:"out"` + Files []string `json:"files"` + Replaced bool `json:"replaced"` + } + if err := json.Unmarshal(runCLI(t, "session", "page", "--out", out, "--json"), &res); err != nil { + t.Fatal(err) + } + if !res.Replaced || len(res.Files) != 6 || res.Out != out { + t.Fatalf("--json = %+v", res) + } + + foreign := t.TempDir() + if err := os.WriteFile(filepath.Join(foreign, "notes.md"), []byte("mine\n"), 0o644); err != nil { + t.Fatal(err) + } + _, err := runCLIStdinErr(t, "", "session", "page", "--out", foreign) + if exitCode(err) != 2 || !strings.Contains(err.Error(), "notes.md") || !strings.Contains(err.Error(), "nothing written") { + t.Fatalf("a foreign --out: %v, want exit 2 naming notes.md", err) + } + if entries, _ := os.ReadDir(foreign); len(entries) != 1 { + t.Fatalf("a refusal wrote into the directory: %v", entries) + } + if _, err := runCLIStdinErr(t, "", "session", "page"); exitCode(err) != 2 { + t.Fatalf("no --out: %v, want exit 2", err) + } +} From 09a5527953ed1dee02d6a74ae86f7d57c919e0e6 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:20:07 +0100 Subject: [PATCH 027/165] docs: mark the brief session spec's page step landed Assisted-by: Claude:claude-opus-5-5 --- ...spc-2610141709430146-the-product-thinker-and-the-technical.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index c40ba8ba10..7f298ccac6 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -88,6 +88,7 @@ The hosting of abcdesign.app is recorded in the plan but not made in this change 3. The page: `abcd session page --out` emits the static page and the vendored, provenance-pinned editor bundle; joining by QR or code, live editing, presence, guest suggestions and approvals, and credit - packages: internal/surface/session/web, internal/surface/cli - tests: internal/surface/session + - landed: f835b6f38 4. Back to GitHub: issue links under the measured length limit, and one pull request with co-author, Suggested-by and Approved-by lines - packages: internal/surface/session/web - tests: internal/surface/session From 565279259da4cbe2216356cc8080543cc84f29ab Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:40:33 +0100 Subject: [PATCH 028/165] fix: refuse a Pages site served below another path than the build's A repository that already served Pages at a custom domain, set up without --domain, got a workflow built for // while GitHub served the site at the domain's root, so every asset would 404 and the gate passed. Inspect now refuses a live custom domain when none was asked for and names the flag to pass. The host stage refuses an address whose path differs from the profile's base path, and setup reads the host before it asks or writes anything, so either refusal lands before the repository half is written. Assisted-by: Claude:claude-opus-5-5 --- .../hosting/githubpages/githubpages.go | 19 +++-- .../hosting/githubpages/githubpages_test.go | 4 + .../githubpages/githubpagestest/fake.go | 14 +++- internal/core/site/setup.go | 54 +++++++++++++ internal/core/site/setup_pages_test.go | 79 ++++++++++++++++++- 5 files changed, 160 insertions(+), 10 deletions(-) diff --git a/internal/adapter/hosting/githubpages/githubpages.go b/internal/adapter/hosting/githubpages/githubpages.go index 0a31da0b31..7e557b2f3f 100644 --- a/internal/adapter/hosting/githubpages/githubpages.go +++ b/internal/adapter/hosting/githubpages/githubpages.go @@ -241,12 +241,19 @@ func (c *client) Inspect(ctx context.Context, s hosting.Site) (hosting.State, er "off it, so set its Pages source to GitHub Actions in the repository's settings and re-run", s.Repo) } st.Exists = true - if s.Domain != "" && p.CNAME != nil && *p.CNAME != "" { - if *p.CNAME != s.Domain { - cname := *p.CNAME - if !domainRe.MatchString(cname) { - cname = "a domain with an unprintable name" - } + if p.CNAME != nil && *p.CNAME != "" { + cname := *p.CNAME + printable := domainRe.MatchString(cname) + if !printable { + cname = "a domain with an unprintable name" + } + switch { + case s.Domain == "" && printable: + // The workflow would build for // while GitHub serves the + // site at the domain's root, so every asset would 404. + return hosting.State{}, fmt.Errorf("github-pages: %s already serves its Pages site at %s; abcd will not move a live domain, so pass --domain %s", + s.Repo, cname, cname) + case *p.CNAME != s.Domain: return hosting.State{}, fmt.Errorf("github-pages: %s already serves its Pages site at %s; abcd will not move a live domain", s.Repo, cname) } st.Routed = true diff --git a/internal/adapter/hosting/githubpages/githubpages_test.go b/internal/adapter/hosting/githubpages/githubpages_test.go index 96e614ed90..831655e0dc 100644 --- a/internal/adapter/hosting/githubpages/githubpages_test.go +++ b/internal/adapter/hosting/githubpages/githubpages_test.go @@ -138,6 +138,10 @@ func TestInspectRefuses(t *testing.T) { "from a branch"}, "a domain routed elsewhere": {githubpagestest.Repo{Pages: &githubpagestest.Pages{BuildType: "workflow", CNAME: "other.example.com"}}, hosting.Site{Name: "widgets", Repo: "acme/widgets", Domain: "docs.example.com"}, "other.example.com"}, + // The workflow would be built for /widgets/ while the site is served at + // the domain's root, so every asset would 404. + "a live domain with none asked for": {githubpagestest.Repo{Pages: &githubpagestest.Pages{BuildType: "workflow", CNAME: "docs.acme.com"}}, + widgets, "already serves its Pages site at docs.acme.com; abcd will not move a live domain, so pass --domain docs.acme.com"}, } { t.Run(name, func(t *testing.T) { f := githubpagestest.New() diff --git a/internal/adapter/hosting/githubpages/githubpagestest/fake.go b/internal/adapter/hosting/githubpages/githubpagestest/fake.go index 342acdc8e6..e773094f7f 100644 --- a/internal/adapter/hosting/githubpages/githubpagestest/fake.go +++ b/internal/adapter/hosting/githubpages/githubpagestest/fake.go @@ -33,6 +33,9 @@ const ( type Pages struct { BuildType string CNAME string + // HTMLURL, when set, is the address the site reports in place of the one + // GitHub would derive from its domain or its repository. + HTMLURL string } // Repo is one repository the fake holds. @@ -105,9 +108,16 @@ func (s *Server) API(_ context.Context, method, path string, body []byte) ([]byt if repo.Pages == nil { return nil, status(404, "Not Found") } - addr := "https://" + strings.ToLower(parts[0]) + ".github.io/" + parts[1] + "/" - if repo.Pages.CNAME != "" { + host := strings.ToLower(parts[0]) + ".github.io" + addr := "https://" + host + "/" + parts[1] + "/" + switch { + case repo.Pages.HTMLURL != "": + addr = repo.Pages.HTMLURL + case repo.Pages.CNAME != "": addr = "https://" + repo.Pages.CNAME + "/" + case strings.EqualFold(parts[1], host): + // The owner's own owner.github.io repository is served at the root. + addr = "https://" + host + "/" } return json.Marshal(map[string]any{"build_type": repo.Pages.BuildType, "cname": nilIfEmpty(repo.Pages.CNAME), "html_url": addr}) case CreatePages: diff --git a/internal/core/site/setup.go b/internal/core/site/setup.go index fd18ca5204..b2985c4d05 100644 --- a/internal/core/site/setup.go +++ b/internal/core/site/setup.go @@ -36,6 +36,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "os" "path/filepath" "regexp" @@ -277,6 +278,14 @@ func Setup(req SetupRequest) (SetupResult, error) { if switchedOff { return setupSwitchedOff(root, branch, adapter, s, req, res) } + // A provider that acts through the forge is read before anything is asked + // or written, so a host it would refuse is refused before the repository + // half is written for an address the site would not be served from. + if adapter.CredentialName() == "" && forge != nil && s.Repo != "" { + if _, err := readForgeHost(ctx, adapter, s); err != nil { + return SetupResult{}, fmt.Errorf("site setup: %s", firstLine(err.Error())) + } + } // The switch (the PT's 2026-10-14 ruling, "ask at setup"): a new // composition naming a provider that publishes only while switched on is // written only on a yes, and then with the switch on. @@ -889,6 +898,9 @@ func setupHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site, for if err != nil { return refuse(err) } + if err := servedAtBase(adapter, s, addr); err != nil { + return refuse(err) + } out.Address = addr out.Status = HostCurrent if len(out.Changes) > 0 { @@ -1016,3 +1028,45 @@ func setupSwitchedOff(root, branch string, adapter hosting.Adapter, s hosting.Si res.Remaining = append(res.Remaining, "commit the removal and push it to "+branch+": `git add "+SiteWorkflowRelPath+"`") return res, nil } + +// readForgeHost is the host's read, made before anything is asked or written +// for a provider that acts through the forge: Inspect, and for a host that +// already exists with nothing left to route, the path it serves the site +// below. A host it would refuse later is refused here. +func readForgeHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site) (hosting.State, error) { + p := adapter.Connect("") + st, err := p.Inspect(ctx, s) + if err != nil || !st.Exists || !st.Routed { + // A host still to be routed changes its address on Route, so its path + // is judged by the host stage after the route. + return st, err + } + addr, err := p.Address(ctx, s) + if err != nil { + return st, err + } + return st, servedAtBase(adapter, s, addr) +} + +// servedAtBase refuses an address the host serves the site below a path other +// than the one the workflow builds it for: every root-absolute link the build +// writes would miss the site. +func servedAtBase(adapter hosting.Adapter, s hosting.Site, addr string) error { + want, err := NormaliseBasePath(adapter.Profile(s).BasePath) + if err != nil { + return err + } + u, err := url.Parse(addr) + if err != nil { + return fmt.Errorf("%s reports an address that is not a URL", adapter.Name()) + } + got := "/" + strings.Trim(u.Path, "/") + "/" + if got == "//" { + got = "/" + } + if got != want { + return fmt.Errorf("%s serves this site at %s, below %s, but the workflow builds it for %s; "+ + "abcd will not publish a site whose every link would miss it", adapter.Name(), addr, got, want) + } + return nil +} diff --git a/internal/core/site/setup_pages_test.go b/internal/core/site/setup_pages_test.go index 05a5e59cd3..86d1aded2d 100644 --- a/internal/core/site/setup_pages_test.go +++ b/internal/core/site/setup_pages_test.go @@ -164,8 +164,10 @@ func TestDecliningTheSwitchChangesNothing(t *testing.T) { if got := changedPaths(t, h.harness); len(got) != 0 { t.Fatalf("a declined switch changed %v", got) } - if len(h.forge.writes) != 0 || len(h.pages.CallLog()) != 0 { - t.Fatalf("a declined switch reached the forge: %v %v", h.forge.writes, h.pages.CallLog()) + // The host is read before the question (the seam's one call before a + // confirmation); nothing is written. + if len(h.forge.writes) != 0 || len(pagesWrites(h)) != 0 { + t.Fatalf("a declined switch wrote to the forge: %v %v", h.forge.writes, pagesWrites(h)) } if len(h.ask.questions) != 1 { t.Fatalf("questions = %q, want the switch alone", h.ask.questions) @@ -341,3 +343,76 @@ func TestThePublishSwitchBelongsToASwitchedProvider(t *testing.T) { t.Fatalf("github-pages refused its own switch: %v", err) } } + +// pagesRequest is the setup request the Pages harness makes, for a test that +// expects Setup itself to refuse. +func pagesRequest(t *testing.T, h *pagesHarness) SetupRequest { + return SetupRequest{ + RepoRoot: h.repo.Root(), Provider: "github-pages", Asker: h.ask, Forge: h.forge, + Adapter: githubpages.Adapter{API: h.pages.API}, Credentials: noCredential{t}, Context: context.Background(), + } +} + +// pagesWrites are the calls that changed the Pages fake: everything but a read. +func pagesWrites(h *pagesHarness) []string { + var out []string + for _, c := range h.pages.CallLog() { + if !strings.HasPrefix(c, "GET ") { + out = append(out, c) + } + } + return out +} + +// refusedBeforeAnything asserts a refusal changed nothing anywhere. +func refusedBeforeAnything(t *testing.T, h *pagesHarness, err error, want string) { + t.Helper() + if err == nil || !strings.Contains(err.Error(), want) || strings.Contains(err.Error(), "\n") { + t.Fatalf("setup = %v, want a one-line refusal naming %q", err, want) + } + if got := changedPaths(t, h.harness); len(got) != 0 { + t.Fatalf("the refused run changed %v", got) + } + if len(h.forge.writes) != 0 || len(pagesWrites(h)) != 0 { + t.Fatalf("the refused run reached the forge: %v %v", h.forge.writes, pagesWrites(h)) + } + if len(h.ask.questions) != 0 { + t.Fatalf("the refused run asked %q", h.ask.questions) + } +} + +// A repository that already serves Pages at a custom domain, set up without +// --domain, would get a workflow built for // while GitHub serves the +// site at the domain's root, so every asset would 404. Setup refuses before +// it asks or writes anything, and says which flag to pass. +func TestALiveDomainWithNoneAskedForIsRefusedBeforeAnything(t *testing.T) { + h := newPagesHarness(t) + h.pages.Repos["example-owner/example-site"].Pages = &githubpagestest.Pages{BuildType: "workflow", CNAME: "docs.example.com"} + _, err := Setup(pagesRequest(t, h)) + refusedBeforeAnything(t, h, err, "pass --domain docs.example.com") +} + +// A Pages site GitHub serves below a path other than the one the workflow +// builds for is refused before anything is written. +func TestASiteServedBelowAnotherPathIsRefusedBeforeAnything(t *testing.T) { + h := newPagesHarness(t) + h.pages.Repos["example-owner/example-site"].Pages = &githubpagestest.Pages{BuildType: "workflow", + HTMLURL: "https://example-owner.github.io/elsewhere/"} + _, err := Setup(pagesRequest(t, h)) + refusedBeforeAnything(t, h, err, "/elsewhere/") +} + +// The host stage holds the same line on its own: an address served below a +// path other than the profile's base path is refused, never reported live. +func TestTheHostStageRefusesASiteServedBelowAnotherPath(t *testing.T) { + f := githubpagestest.New() + f.Repos["acme/widgets"].Pages = &githubpagestest.Pages{BuildType: "workflow", HTMLURL: "https://acme.github.io/elsewhere/"} + out, declined, refused := setupHost(context.Background(), githubpages.Adapter{API: f.API}, + hosting.Site{Name: "widgets", Repo: "acme/widgets"}, true, SetupRequest{Asker: answer(true)}) + if !refused || declined || out.Status != HostRefused { + t.Fatalf("host = %+v (declined %v, refused %v), want refused", out, declined, refused) + } + if out.Address != "" || !strings.Contains(out.Detail, "/elsewhere/") || !strings.Contains(out.Detail, "/widgets/") { + t.Fatalf("host = %+v, want no address and a detail naming both paths", out) + } +} From c6f3d798aa8e2c572dd059de76cd6ab8d152cfcf Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:41:50 +0100 Subject: [PATCH 029/165] fix: name an existing Pages site before publishing over it A repository that already published a workflow-built Pages site had it taken over silently. Inspect now names an existing site by its address and build type. Setup names it in the switch question, or asks on its own when the composition already holds the switch and the release workflow is not there yet; a no writes nothing, and --yes is consent. The host outcome names the site it publishes to. Assisted-by: Claude:claude-opus-5-5 --- .../hosting/githubpages/githubpages.go | 6 ++ .../hosting/githubpages/githubpages_test.go | 6 ++ internal/adapter/hosting/hosting.go | 4 ++ internal/core/site/setup.go | 61 +++++++++++++++---- internal/core/site/setup_pages_test.go | 54 ++++++++++++++++ 5 files changed, 120 insertions(+), 11 deletions(-) diff --git a/internal/adapter/hosting/githubpages/githubpages.go b/internal/adapter/hosting/githubpages/githubpages.go index 7e557b2f3f..1bbb752133 100644 --- a/internal/adapter/hosting/githubpages/githubpages.go +++ b/internal/adapter/hosting/githubpages/githubpages.go @@ -210,6 +210,7 @@ func (c *client) Verify(context.Context) error { // Inspect reads whether the repository is public, whether its Pages site // exists and is built by a workflow, and whether the domain is routed to it. +// An existing site is named by its address and build type. func (c *client) Inspect(ctx context.Context, s hosting.Site) (hosting.State, error) { if err := checkSite(s); err != nil { return hosting.State{}, err @@ -241,6 +242,11 @@ func (c *client) Inspect(ctx context.Context, s hosting.Site) (hosting.State, er "off it, so set its Pages source to GitHub Actions in the repository's settings and re-run", s.Repo) } st.Exists = true + addr := p.HTMLURL + if !addrRe.MatchString(addr) { + addr = "an address that is not a plain https URL" + } + st.Existing = addr + " (build type " + p.BuildType + ")" if p.CNAME != nil && *p.CNAME != "" { cname := *p.CNAME printable := domainRe.MatchString(cname) diff --git a/internal/adapter/hosting/githubpages/githubpages_test.go b/internal/adapter/hosting/githubpages/githubpages_test.go index 831655e0dc..7727f68d34 100644 --- a/internal/adapter/hosting/githubpages/githubpages_test.go +++ b/internal/adapter/hosting/githubpages/githubpages_test.go @@ -86,9 +86,15 @@ func TestInspectCreateAndAddressAProjectSite(t *testing.T) { if got := f.Repos["acme/widgets"].Pages; got == nil || got.BuildType != "workflow" { t.Fatalf("pages = %+v, want a site built by the workflow", got) } + if st.Existing != "" { + t.Fatalf("inspect named an existing site where there is none: %q", st.Existing) + } if st, err = p.Inspect(context.Background(), widgets); err != nil || !st.Exists { t.Fatalf("inspect after create = %+v, %v", st, err) } + if st.Existing != "https://acme.github.io/widgets/ (build type workflow)" { + t.Fatalf("existing = %q, want the site's address and build type", st.Existing) + } addr, err := p.Address(context.Background(), widgets) if err != nil || addr != "https://acme.github.io/widgets/" { t.Fatalf("address = %q, %v", addr, err) diff --git a/internal/adapter/hosting/hosting.go b/internal/adapter/hosting/hosting.go index 427ea80b69..5aaf4f0e1a 100644 --- a/internal/adapter/hosting/hosting.go +++ b/internal/adapter/hosting/hosting.go @@ -70,6 +70,10 @@ type State struct { // Routed is true when the domain is already routed to the host, and // vacuously true when the site asks for no domain. Routed bool + // Existing names the site the host already serves, by its address and + // how it is built, when the provider can say so; empty when the host + // does not exist yet. The verb names it before publishing over it. + Existing string } // Provider is the live half of the seam: one connected account. diff --git a/internal/core/site/setup.go b/internal/core/site/setup.go index b2985c4d05..5d410b62a6 100644 --- a/internal/core/site/setup.go +++ b/internal/core/site/setup.go @@ -281,22 +281,34 @@ func Setup(req SetupRequest) (SetupResult, error) { // A provider that acts through the forge is read before anything is asked // or written, so a host it would refuse is refused before the repository // half is written for an address the site would not be served from. + takeover := "" if adapter.CredentialName() == "" && forge != nil && s.Repo != "" { - if _, err := readForgeHost(ctx, adapter, s); err != nil { + st, err := readForgeHost(ctx, adapter, s) + if err != nil { return SetupResult{}, fmt.Errorf("site setup: %s", firstLine(err.Error())) } + // A site the host already serves, in a repository setup has not + // added its workflow to, is someone's: it is published over only + // after a question naming it. + if st.Existing != "" { + if _, serr := os.Lstat(filepath.Join(root, filepath.FromSlash(SiteWorkflowRelPath))); os.IsNotExist(serr) { + takeover = st.Existing + } + } + } + if takeover != "" && !(prof.Switched && manifestBytes != nil) { + if req.Asker == nil || !req.Asker.Confirm(takeoverQuestion(adapter, takeover)) { + return declinedBeforeWriting(res, "the existing "+adapter.Name()+" site at "+takeover+" was not published over", + "re-run `abcd site setup` and answer yes, to publish over the existing site"), nil + } } // The switch (the PT's 2026-10-14 ruling, "ask at setup"): a new // composition naming a provider that publishes only while switched on is // written only on a yes, and then with the switch on. if prof.Switched && manifestBytes != nil { - if req.Asker == nil || !req.Asker.Confirm(switchQuestion(adapter, prof)) { - res.Status = StatusDeclined - res.Files, res.Environments = []scaffold.FileOutcome{}, []EnvironmentOutcome{} - res.Host.Detail = "not reached: the project site was not switched on" - res.Notes = append(res.Notes, "the project site was not switched on, so nothing was written and nothing remote was changed") - res.Remaining = append(res.Remaining, "re-run `abcd site setup --provider "+adapter.Name()+"` and answer yes, to switch the project site on") - return res, nil + if req.Asker == nil || !req.Asker.Confirm(switchQuestion(adapter, prof, takeover)) { + return declinedBeforeWriting(res, "the project site was not switched on", + "re-run `abcd site setup --provider "+adapter.Name()+"` and answer yes, to switch the project site on"), nil } on := true manifest.Hosting.Publish, hostingBlock.Publish = &on, &on @@ -872,6 +884,9 @@ func setupHost(ctx context.Context, adapter hosting.Adapter, s hosting.Site, for if err != nil { return refuse(err) } + if st.Existing != "" { + out.Detail = "publishes to the " + adapter.Name() + " site already at " + st.Existing + } if !st.Exists { out.Changes = append(out.Changes, "create "+adapter.Name()+" host "+s.Name) } @@ -962,15 +977,39 @@ func firstLine(s string) string { return line } -// switchQuestion is the switch, asked once, before anything is written. -func switchQuestion(adapter hosting.Adapter, prof hosting.Profile) string { +// switchQuestion is the switch, asked once, before anything is written. A +// site the host already serves (takeover, its address and build type) is +// named in it, since a yes publishes over that site. +func switchQuestion(adapter hosting.Adapter, prof hosting.Profile, takeover string) string { where := adapter.Name() if prof.Address != "" { where = prof.Address + " (" + adapter.Name() + ")" } - return "Switch on the project site? At each release this repository's own workflow builds its about page and development record " + + q := "Switch on the project site? At each release this repository's own workflow builds its about page and development record " + "and publishes them at " + where + ". Yes records the switch in " + ManifestRelPath + ", writes the site's inputs under site-src/ " + "and adds " + SiteWorkflowRelPath + "; nothing else in the repository changes" + if takeover != "" { + q += ". This repository already publishes a site there, at " + takeover + ", and yes replaces it at the next release" + } + return q +} + +// takeoverQuestion asks before publishing over a site the host already +// serves, when no switch question is asked to name it. +func takeoverQuestion(adapter hosting.Adapter, takeover string) string { + return "Publish over the existing " + adapter.Name() + " site? This repository already publishes one, at " + takeover + + ", and abcd's release workflow (" + SiteWorkflowRelPath + ") replaces it at the next release" +} + +// declinedBeforeWriting is a run declined before anything was written or +// changed remotely: why, and the step that takes it further. +func declinedBeforeWriting(res SetupResult, why, step string) SetupResult { + res.Status = StatusDeclined + res.Files, res.Environments = []scaffold.FileOutcome{}, []EnvironmentOutcome{} + res.Host.Detail = "not reached: " + why + res.Notes = append(res.Notes, why+", so nothing was written and nothing remote was changed") + res.Remaining = append(res.Remaining, step) + return res } // setupSwitchedOff is setup for a project site whose switch is off: the next diff --git a/internal/core/site/setup_pages_test.go b/internal/core/site/setup_pages_test.go index 86d1aded2d..5d60b55eb5 100644 --- a/internal/core/site/setup_pages_test.go +++ b/internal/core/site/setup_pages_test.go @@ -416,3 +416,57 @@ func TestTheHostStageRefusesASiteServedBelowAnotherPath(t *testing.T) { t.Fatalf("host = %+v, want no address and a detail naming both paths", out) } } + +// A repository that already publishes a Pages site built by a workflow is +// not taken over silently: the switch question names the site's address and +// build type, a no leaves everything as it was, and the host outcome names it. +func TestAnExistingPagesSiteIsNamedBeforeItIsPublishedOver(t *testing.T) { + existing := "https://example-owner.github.io/example-site/ (build type workflow)" + t.Run("declined", func(t *testing.T) { + h := newPagesHarness(t) + h.pages.Repos["example-owner/example-site"].Pages = &githubpagestest.Pages{BuildType: "workflow"} + h.ask.yes = false + res := h.run(t) + if len(h.ask.questions) != 1 || !strings.Contains(h.ask.questions[0], existing) { + t.Fatalf("questions = %q, want the switch naming %q", h.ask.questions, existing) + } + if res.Status != StatusDeclined || len(changedPaths(t, h.harness)) != 0 || len(pagesWrites(h)) != 0 || len(h.forge.writes) != 0 { + t.Fatalf("a declined takeover = %q, changed %v, wrote %v %v", res.Status, changedPaths(t, h.harness), pagesWrites(h), h.forge.writes) + } + }) + t.Run("confirmed", func(t *testing.T) { + h := newPagesHarness(t) + h.pages.Repos["example-owner/example-site"].Pages = &githubpagestest.Pages{BuildType: "workflow"} + res := h.run(t) + if len(h.ask.questions) == 0 || !strings.Contains(h.ask.questions[0], existing) { + t.Fatalf("questions = %q, want the switch naming %q", h.ask.questions, existing) + } + if res.Host.Status != HostCurrent || !strings.Contains(res.Host.Detail, existing) { + t.Fatalf("host = %+v, want the outcome naming %q", res.Host, existing) + } + }) + // A composition that already holds the switch is asked no switch + // question, so the takeover is asked on its own before anything is + // written. + t.Run("composition already switched on", func(t *testing.T) { + h := newPagesHarness(t) + h.run(t) + h.repo.Git("add", "-A") + h.repo.Git("commit", "-q", "-m", "switch the project site on") + if err := os.Remove(filepath.Join(h.repo.Root(), SiteWorkflowRelPath)); err != nil { + t.Fatal(err) + } + h.ask.questions, h.ask.yes = nil, false + writes := len(pagesWrites(h)) + res := h.run(t) + if len(h.ask.questions) != 1 || !strings.Contains(h.ask.questions[0], existing) { + t.Fatalf("questions = %q, want the takeover naming %q", h.ask.questions, existing) + } + if res.Status != StatusDeclined || len(pagesWrites(h)) != writes { + t.Fatalf("a declined takeover = %q, wrote %v", res.Status, pagesWrites(h)[writes:]) + } + if got := changedPaths(t, h.harness); !reflect.DeepEqual(got, []string{SiteWorkflowRelPath}) { + t.Fatalf("a declined takeover changed %v, want only the workflow the test removed", got) + } + }) +} From bfa3601c1ac4e5df58a48628e77c509e09dcbe63 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:44:26 +0100 Subject: [PATCH 030/165] feat: keep guests' suggestions in the session document A guest's suggestion reached the members connected when it was sent and lived only in their pages' memory, so a member who joined later never saw it. The first member page to receive a suggestion now records it in the shared document's suggestions map, once, and every member's panel reads the pending suggestions from there; suggestions_done stays the record of each answer. Assisted-by: Claude:claude-opus-5-5 --- internal/surface/session/web/session.js | 35 ++++++++++++------- internal/surface/session/web/web_test.go | 43 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/internal/surface/session/web/session.js b/internal/surface/session/web/session.js index 2aa9cb08d6..782f54b8e7 100644 --- a/internal/surface/session/web/session.js +++ b/internal/surface/session/web/session.js @@ -219,6 +219,9 @@ const doc = new Y.Doc({ gc: false }); const files = doc.getMap("files"); const meta = doc.getMap("meta"); const approvals = doc.getArray("approvals"); +// A guest's suggestion, recorded by the first team member whose page saw it, +// so a member who joins later sees it too; suggestions_done holds its answer. +const suggestions = doc.getMap("suggestions"); const suggestionsDone = doc.getMap("suggestions_done"); const awareness = new Awareness(doc); @@ -245,7 +248,6 @@ const session = { roster: [], credits: {}, // Yjs client id -> login, from the relay's roster }; -const incoming = new Map(); // suggestion id -> {id, from, body}, members only const mine = []; // a guest's own suggestions const awaitingAck = []; // suggestions sent, waiting for "suggested" let ws = null; @@ -833,11 +835,22 @@ function validSuggestion(b) { typeof (b.quote.prefix ?? "") === "string" && typeof (b.quote.suffix ?? "") === "string"; } +// storedSuggestion keeps the fields a suggestion is made of, and nothing else. +function storedSuggestion(id, from, b) { + const quote = { exact: b.quote.exact, prefix: b.quote.prefix || "", suffix: b.quote.suffix || "" }; + return { id, from, body: { path: b.path, anchor: b.anchor ?? null, head: b.head ?? null, quote, replacement: b.replacement } }; +} + function onSuggest(msg) { if (session.role !== "member") return; const id = String(msg.id || ""); if (!id || !validSuggestion(msg.body)) return; - incoming.set(id, { id, from: typeof msg.from === "string" ? msg.from : "Guest", body: msg.body }); + const from = typeof msg.from === "string" && msg.from ? msg.from : "Guest"; + // Every member's page sees the suggestion; the first to record it wins, and + // the others find it there already. + if (!suggestions.has(id) && !suggestionsDone.has(id)) { + doc.transact(() => { suggestions.set(id, storedSuggestion(id, from, msg.body)); }); + } renderSide(); } @@ -883,8 +896,8 @@ function locate(body) { } function applySuggestion(id) { - const s = incoming.get(id); - if (!s || session.role !== "member" || suggestionsDone.has(id)) return; + const s = suggestions.get(id); + if (!s || !validSuggestion(s.body) || session.role !== "member" || suggestionsDone.has(id)) return; const place = locate(s.body); if (!place) { toast(WORDS.cannotApply); return; } doc.transact(() => { @@ -894,7 +907,7 @@ function applySuggestion(id) { approvals.push([{ id, path: s.body.path, - guest: s.from, + guest: typeof s.from === "string" ? s.from : "Guest", approver: session.login, before, after: s.body.replacement, @@ -902,20 +915,16 @@ function applySuggestion(id) { }]); suggestionsDone.set(id, "applied"); }); - incoming.delete(id); renderSide(); } function declineSuggestion(id) { if (session.role !== "member") return; if (!suggestionsDone.has(id)) doc.transact(() => { suggestionsDone.set(id, "declined"); }); - incoming.delete(id); renderSide(); } -suggestionsDone.observe(() => { - for (const id of suggestionsDone.keys()) incoming.delete(id); - renderSide(); -}); +suggestions.observe(() => renderSide()); +suggestionsDone.observe(() => renderSide()); // --------------------------------------------------------------------------- // Step 4's hook: sending the session back to GitHub. meta.mode, the @@ -1192,8 +1201,8 @@ function renderSide() { if (session.role === "member" && session.synced) { const items = el("ul"); - for (const s of incoming.values()) { - if (suggestionsDone.has(s.id)) continue; + for (const s of suggestions.values()) { + if (!s || typeof s.id !== "string" || suggestionsDone.has(s.id) || !validSuggestion(s.body)) continue; items.append(el("li", { class: "suggestion" }, el("div", { class: "from", text: WORDS.suggestedBy + " " + s.from + " · " + s.body.path.replace(BRIEF_PREFIX, "") }), el("del", { text: s.body.quote.exact }), diff --git a/internal/surface/session/web/web_test.go b/internal/surface/session/web/web_test.go index 51c9211bde..00474e3caf 100644 --- a/internal/surface/session/web/web_test.go +++ b/internal/surface/session/web/web_test.go @@ -391,3 +391,46 @@ func TestPageScriptKeepsTheTokenAndTheDOMSafe(t *testing.T) { t.Error("session.js has no submitSession hook") } } + +// pageScript is session.js as the binary embeds it. +func pageScript(t *testing.T) string { + t.Helper() + raw, err := assets.ReadFile("session.js") + if err != nil { + t.Fatal(err) + } + return string(raw) +} + +// functionBody returns the source of the named top-level function, from its +// declaration to the closing brace at the start of a line. +func functionBody(t *testing.T, js, name string) string { + t.Helper() + m := regexp.MustCompile(`(?m)^(?:async )?function ` + regexp.QuoteMeta(name) + `\(`).FindStringIndex(js) + if m == nil { + t.Fatalf("session.js has no function %s", name) + } + rest := js[m[0]:] + end := strings.Index(rest, "\n}\n") + if end < 0 { + t.Fatalf("function %s does not end", name) + } + return rest[:end+2] +} + +func TestSuggestionsLiveInTheDocument(t *testing.T) { + js := pageScript(t) + if !strings.Contains(js, `const suggestions = doc.getMap("suggestions");`) { + t.Fatal("suggestions are not a map in the shared document") + } + on := functionBody(t, js, "onSuggest") + if !strings.Contains(on, "!suggestions.has(id)") || !strings.Contains(on, "suggestions.set(id, ") { + t.Errorf("onSuggest does not record a suggestion in the document once:\n%s", on) + } + if strings.Contains(js, "incoming") { + t.Error("session.js still keeps suggestions in page memory (incoming)") + } + if !strings.Contains(functionBody(t, js, "renderSide"), "suggestions.values()") { + t.Error("the members' panel does not read the suggestions from the document") + } +} From a30ec94327f88d1d06f46fb2b8d811955d5751d0 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:44:38 +0100 Subject: [PATCH 031/165] feat: send a brief session back to GitHub as issues or one pull request The session page's "Send to GitHub" panel lists every passage changed since the session's base snapshot, with its credit, and offers the route the host chose (spc-2610141709430146 step 4). Credit reads Y.Text's delta between the base snapshot and now: an inserted run is credited to the login the relay's roster gives its Yjs client id, a deleted run to the members whose pages recorded deleting it (Yjs keeps no deleter, so each member page records its own), and a run an approval made to the approver and the guest. Changed runs within one unchanged line of each other form one passage of whole lines. Issues: each passage becomes a pre-filled issue link carrying the file, the base commit, the passage before and after, and its credit, with no labels parameter. The link is measured whole, encoded, in bytes, against ISSUE_URL_LIMIT (7,600); a passage over it is offered as its own pull request instead. One pull request: with the submitter's token the page creates abcdesign/- at the base commit, makes one commit on it through createCommitOnBranch with the changed files' UTF-8 contents in base64, crediting every other member with a Co-authored-by line and each approved guest change with Suggested-by and Approved-by lines on noreply addresses, then opens the pull request. Each failure is a plain message, and a branch whose commit failed is deleted again. Assisted-by: Claude:claude-opus-5-5 --- internal/surface/session/web/session.css | 14 + internal/surface/session/web/session.js | 591 ++++++++++++++++++++++- internal/surface/session/web/web_test.go | 106 +++- 3 files changed, 698 insertions(+), 13 deletions(-) diff --git a/internal/surface/session/web/session.css b/internal/surface/session/web/session.css index ccf55887d4..749c421781 100644 --- a/internal/surface/session/web/session.css +++ b/internal/surface/session/web/session.css @@ -102,6 +102,20 @@ label { display: block; font-size: .85rem; color: var(--muted); margin: .6rem 0 .suggestion del { background: rgba(220, 38, 38, .12); } .suggestion ins { background: rgba(22, 163, 74, .12); } .state { font-size: .8rem; color: var(--muted); } +.state.bad { color: var(--warn); } + +/* Sending the session to GitHub. */ +.send { border-bottom: 1px solid var(--line); padding: .8rem 1rem; background: var(--panel); max-height: 60vh; overflow: auto; } +.send h2 { font-size: 1rem; margin: 0 0 .3rem; } +.send ul { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .6rem; } +.passage { border: 1px solid var(--line); border-radius: var(--radius); padding: .5rem; font-size: .9rem; } +.passage .from { font-size: .8rem; color: var(--muted); word-break: break-all; } +.passage .credit { font-size: .85rem; white-space: pre-line; margin: .2rem 0; } +.passage del, .passage ins { display: block; white-space: pre-wrap; text-decoration: none; padding: .2rem .3rem; border-radius: 4px; margin: .2rem 0; max-height: 12rem; overflow: auto; } +.passage del { background: rgba(220, 38, 38, .12); } +.passage ins { background: rgba(22, 163, 74, .12); } +a.button { text-decoration: none; display: inline-block; } +.send .state a { word-break: break-all; } .toast { position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%); diff --git a/internal/surface/session/web/session.js b/internal/surface/session/web/session.js index 782f54b8e7..4717352952 100644 --- a/internal/surface/session/web/session.js +++ b/internal/surface/session/web/session.js @@ -1,13 +1,16 @@ -// The brief session page (itd-2610091016262335, spc-2610141709430146 step 3). +// The brief session page (itd-2610091016262335, spc-2610141709430146 steps 3 +// and 4). // // A team member hosts a live session on the team's own relay; everyone else // joins by the QR code or the six-character code. Team members edit the brief // directly and their edits are credited to them; a guest proposes a -// replacement for a passage, which a team member applies or declines. +// replacement for a passage, which a team member applies or declines. The +// session goes back to GitHub as one pre-filled issue per changed passage, or +// as one pull request made with the submitter's own token. // // What this page talks to, and nothing else: the relay the person names (its // WebSocket, /config and /auth/token), https://api.github.com with the -// person's own token, and github.com for sign-in. The GitHub token is held in +// person's own token, and github.com for sign-in and pre-filled issues. The GitHub token is held in // this module's memory only; it is never stored, put in a URL or logged. // Every piece of text from the brief, the relay or another participant is // rendered with textContent. @@ -91,14 +94,48 @@ const WORDS = Object.freeze({ noMembers: "No team member is connected to receive it.", mySuggestionsHeading: "Your suggestions", submit: "Send to GitHub", - submitComingNext: "Sending the session to GitHub is coming in the next step.", + sendHeading: "Send to GitHub", + sendIntroPR: "The session goes back as one pull request, with every credited team member named on its commit.", + sendIntroIssues: "Each changed passage goes back as its own issue. GitHub opens the issue for you to check before you submit it.", + sendNothing: "Nothing has changed since the session began.", + sendRefresh: "Refresh", + sendClose: "Close", + sendAsIssue: "Send as issue", + tooLongForIssue: "Too long for an issue, send as a pull request", + sendAsPR: "Send as a pull request", + openPR: "Open the pull request", + prNeedsSignIn: "Only a team member signed in with GitHub can open the pull request.", + linesAt: "lines", + lineAt: "line", + creditUnknown: "A participant the session could not credit", + prWorking: "Opening the pull request…", + prOpened: "Pull request opened:", + passageMoved: "That passage has changed since the panel showed it. Check it again, then send.", + prNoBase: "This session does not know which commit it started from, so it cannot open a pull request.", + ghUnreachable: "GitHub could not be reached. Check your connection and try again.", + ghSignIn: "GitHub did not accept your sign-in. Sign in again, then send.", + ghScope: "GitHub refused: your sign-in may not create branches, commits or pull requests in this repository. Sign in again with access to its contents and pull requests.", + ghNotFound: "GitHub could not find the repository, the branch or a team member, or your sign-in cannot see them.", + ghStale: "The new branch moved on GitHub before the commit was made. Try again.", + ghRefTaken: "GitHub would not create the session's branch; one by that name may already exist. Try again.", + ghPRRefused: "The commit is on the branch, but GitHub would not open the pull request. Open it on GitHub from branch", + ghOther: "GitHub did not complete the request. Try again in a moment.", }); // --------------------------------------------------------------------------- // Fixed values. // --------------------------------------------------------------------------- const GITHUB_API = "https://api.github.com"; +const GITHUB_WEB = "https://github.com"; const GITHUB_AUTHORIZE = "https://github.com/login/oauth/authorize"; +// GitHub answers 414 to a pre-filled issue link over its server limit and does +// not document the number; the GitHub CLI stops at 8,192 bytes. The link is +// measured as GitHub receives it, encoded, in bytes, against this margin +// (research note 2026-10-14-github-roles-admission-and-issue-links.md). +const ISSUE_URL_LIMIT = 7600; +const COMMIT_HEADLINE = "Edit the brief in a live session"; +// Passages closer than this many unchanged lines are one passage. +const PASSAGE_JOIN_LINES = 1; const BRIEF_DIR = [".abcd", "development", "brief"]; const BRIEF_PREFIX = BRIEF_DIR.join("/") + "/"; const SIGN_IN_SCOPE = "repo"; @@ -223,6 +260,9 @@ const approvals = doc.getArray("approvals"); // so a member who joins later sees it too; suggestions_done holds its answer. const suggestions = doc.getMap("suggestions"); const suggestionsDone = doc.getMap("suggestions_done"); +// Yjs records who inserted each piece of text but not who deleted it: each +// member's page records its own deletions here, under its Yjs client id. +const deletions = new Y.PermanentUserData(doc, doc.getMap("deletions")); const awareness = new Awareness(doc); // --------------------------------------------------------------------------- @@ -437,6 +477,7 @@ function becomeMember() { const was = session.role; session.role = "member"; sendJSON({ type: "register", client: doc.clientID }); + trackDeletions(); const queued = outbox; outbox = []; for (const frame of queued) { @@ -774,6 +815,7 @@ async function hostBrief() { const brief = await loadBrief(); setStatus(WORDS.sendingBrief, false); seed(brief, ui.mode ? ui.mode.value : "pr"); + trackDeletions(); await drained(); session.seeding = false; session.synced = true; @@ -900,17 +942,26 @@ function applySuggestion(id) { if (!s || !validSuggestion(s.body) || session.role !== "member" || suggestionsDone.has(id)) return; const place = locate(s.body); if (!place) { toast(WORDS.cannotApply); return; } - doc.transact(() => { + const replacement = s.body.replacement; + doc.transact((tr) => { const before = place.ytext.toString().slice(place.from, place.to); place.ytext.delete(place.from, place.to - place.from); - place.ytext.insert(place.from, s.body.replacement); + // The ids of the text the approval removed and inserted, so the credit + // can tell an approved change from the approver's own typing. + const del = []; + tr.deleteSet.clients.forEach((items, client) => { for (const d of items) del.push([client, d.clock, d.len]); }); + const clock = Y.getState(doc.store, doc.clientID); + place.ytext.insert(place.from, replacement); approvals.push([{ id, path: s.body.path, guest: typeof s.from === "string" ? s.from : "Guest", approver: session.login, + client: doc.clientID, + ins: replacement ? [doc.clientID, clock, replacement.length] : null, + del, before, - after: s.body.replacement, + after: replacement, at: new Date().toISOString(), }]); suggestionsDone.set(id, "applied"); @@ -927,13 +978,524 @@ suggestions.observe(() => renderSide()); suggestionsDone.observe(() => renderSide()); // --------------------------------------------------------------------------- -// Step 4's hook: sending the session back to GitHub. meta.mode, the -// approvals array, the base snapshot and session.credits are what it reads. +// Credit: what changed since the session began, and who changed it. +// +// The base is the snapshot the host took right after seeding. Y.Text's +// toDelta(now, base) marks each piece of text added or removed since then, +// with the Yjs item it belongs to; the item's client id names who inserted +// it, and the members' recorded deletions name who removed it. The relay's +// roster maps each member's client ids to their GitHub login. +// --------------------------------------------------------------------------- +let deletionsTracked = false; +function trackDeletions() { + if (deletionsTracked) return; + deletionsTracked = true; + deletions.setUserMapping(doc, doc.clientID, String(doc.clientID), { filter: (tr) => touchesBrief(tr) }); +} +function touchesBrief(tr) { + const texts = new Set(files.values()); + for (const type of tr.changed.keys()) if (texts.has(type)) return true; + return false; +} + +let baseCache = { raw: null, snap: null }; +function baseSnapshot() { + const raw = meta.get("base_snapshot"); + if (!(raw instanceof Uint8Array)) return null; + if (baseCache.raw !== raw) { + try { baseCache = { raw, snap: Y.decodeSnapshot(raw) }; } catch { return null; } + } + return baseCache.snap; +} + +function loginOf(client) { + const login = session.credits[String(client)]; + return typeof login === "string" && NAME_RE.test(login) ? login : ""; +} + +// changesOf lists a file's text as runs: "same" since the base, "add" or +// "del", each changed run one Yjs item with its client and clock. +function changesOf(ytext, base) { + const delta = ytext.toDelta(Y.snapshot(doc), base, (type, id) => ({ type, client: id.client, clock: id.clock })); + const ops = []; + for (const op of delta) { + if (typeof op.insert !== "string") continue; + const ch = op.attributes && op.attributes.ychange; + if (!ch) ops.push({ kind: "same", text: op.insert }); + else ops.push({ kind: ch.type === "added" ? "add" : "del", text: op.insert, client: ch.client, clock: ch.clock }); + } + return ops; +} + +function newlines(s) { + let n = 0; + for (let i = s.indexOf("\n"); i !== -1; i = s.indexOf("\n", i + 1)) n++; + return n; +} + +function overlaps(clock, len, from, n) { + return clock < from + n && from < clock + len; +} + +// approvalOf names the approval a changed run belongs to, if any. +function approvalOf(path, op, list) { + for (const a of list) { + if (!a || a.path !== path) continue; + if (op.kind === "add" && Array.isArray(a.ins) && a.ins[0] === op.client && overlaps(op.clock, op.text.length, a.ins[1], a.ins[2])) return a; + if (op.kind === "del" && Array.isArray(a.del) && a.del.some((d) => Array.isArray(d) && d[0] === op.client && overlaps(op.clock, op.text.length, d[1], d[2]))) return a; + } + return null; +} + +// deletersOf names the members whose pages recorded deleting a removed run. +function deletersOf(op) { + const out = []; + for (const [client, ds] of deletions.dss) { + const ranges = ds.clients.get(op.client) || []; + if (ranges.some((r) => overlaps(op.clock, op.text.length, r.clock, r.len))) out.push(client); + } + return out; +} + +// makePassage turns the changed runs ops[i..j] into a passage: whole lines +// before and after, where they sit in the base, and who to credit. +function makePassage(path, ops, offsets, i, j, approvalList) { + const prev = i > 0 ? ops[i - 1].text : ""; + const left = prev.slice(prev.lastIndexOf("\n") + 1); + let before = left; + let after = left; + const authors = new Set(); + const approved = new Map(); + let unknown = false; + for (let k = i; k <= j; k++) { + const op = ops[k]; + if (op.kind !== "del") after += op.text; + if (op.kind !== "add") before += op.text; + if (op.kind === "same") continue; + const a = approvalOf(path, op, approvalList); + if (a) { + const approver = loginOf(a.client) || (typeof a.approver === "string" ? a.approver : ""); + const guest = trailerText(a.guest); + approved.set(guest + "\u0000" + approver, { guest, approver }); + continue; + } + const who = op.kind === "add" ? [op.client] : deletersOf(op); + let named = false; + for (const c of who) { + const login = loginOf(c); + if (login) { authors.add(login); named = true; } + } + if (!named) unknown = true; + } + const lineEnded = (s) => s === "" || s.endsWith("\n"); + if (j + 1 < ops.length && !(lineEnded(before) && lineEnded(after))) { + const next = ops[j + 1].text; + const cut = next.indexOf("\n"); + const right = cut < 0 ? next : next.slice(0, cut); + before += right; + after += right; + } + const baseStart = offsets[i].base - left.length; + return { + path, + baseStart, + baseEnd: baseStart + before.length, + before, + after, + authors: [...authors].sort(), + approvals: [...approved.values()], + unknown, + }; +} + +// analyse reads every file's changes since the base, as passages. +function analyse() { + const base = baseSnapshot(); + const out = []; + if (!base) return out; + const approvalList = approvals.toArray(); + for (const path of [...files.keys()].sort()) { + const ytext = files.get(path); + if (!(ytext instanceof Y.Text)) continue; + const ops = changesOf(ytext, base); + if (!ops.some((op) => op.kind !== "same")) continue; + const offsets = []; + let b = 0; + let c = 0; + for (const op of ops) { + offsets.push({ base: b, cur: c }); + if (op.kind !== "add") b += op.text.length; + if (op.kind !== "del") c += op.text.length; + } + const baseText = ops.filter((op) => op.kind !== "add").map((op) => op.text).join(""); + const passages = []; + let i = 0; + while (i < ops.length) { + if (ops[i].kind === "same") { i++; continue; } + let j = i; + let k = i + 1; + for (;;) { + while (k < ops.length && ops[k].kind !== "same") { j = k; k++; } + // ops[k] is unchanged text; one more changed run after it, within + // PASSAGE_JOIN_LINES unchanged lines, joins this passage. + if (k + 1 < ops.length && newlines(ops[k].text) <= PASSAGE_JOIN_LINES + 1) { j = k + 1; k += 2; continue; } + break; + } + const p = makePassage(path, ops, offsets, i, j, approvalList); + p.lineFrom = 1 + newlines(baseText.slice(0, p.baseStart)); + p.lineTo = p.lineFrom + Math.max(0, newlines(p.before.replace(/\n$/, ""))); + passages.push(p); + i = j + 1; + } + const current = ops.filter((op) => op.kind !== "del").map((op) => op.text).join(""); + if (current !== baseText) out.push({ path, base: baseText, current, passages }); + } + return out; +} + +function relPath(path) { + return path.startsWith(BRIEF_PREFIX) ? path.slice(BRIEF_PREFIX.length) : path; +} +function linesLabel(p) { + return p.lineTo > p.lineFrom ? WORDS.linesAt + " " + p.lineFrom + "–" + p.lineTo : WORDS.lineAt + " " + p.lineFrom; +} + +// creditLines says who a passage is credited to, one line each. +function creditLines(p) { + const lines = []; + if (p.authors.length) lines.push("Written by " + p.authors.map((l) => "@" + l).join(", ")); + for (const a of p.approvals) lines.push("Suggested by " + a.guest + " (guest), approved by @" + a.approver); + if (p.unknown && lines.length === 0) lines.push(WORDS.creditUnknown); + return lines; +} + +// --------------------------------------------------------------------------- +// The issues route: one pre-filled GitHub issue link per passage. +// --------------------------------------------------------------------------- +// fenced wraps text in a code fence longer than any run of backticks in it. +function fenced(text) { + let longest = 0; + for (const m of text.matchAll(/`+/g)) longest = Math.max(longest, m[0].length); + const fence = "`".repeat(Math.max(3, longest + 1)); + return fence + "markdown\n" + text + (text === "" || text.endsWith("\n") ? "" : "\n") + fence; +} + +function issueTitle(p) { + return "Brief: " + relPath(p.path) + ", " + linesLabel(p); +} + +function issueBody(p) { + return [ + "A change to the brief, proposed in a live session.", + "", + "- File: `" + p.path + "`", + "- Base commit: " + meta.get("base_sha"), + "- At the base commit: " + linesLabel(p), + "", + "Before:", + "", + fenced(p.before), + "", + "After:", + "", + fenced(p.after), + "", + ...creditLines(p), + ].join("\n"); +} + +// issueLink builds the passage's pre-filled issue link and says whether it +// fits: measured whole, as GitHub receives it, after encoding, in bytes. +function issueLink(p) { + const url = GITHUB_WEB + "/" + encodeURIComponent(route.owner) + "/" + encodeURIComponent(route.repo) + + "/issues/new?title=" + encodeURIComponent(issueTitle(p)) + "&body=" + encodeURIComponent(issueBody(p)); + return { url, fits: utf8Length(url) <= ISSUE_URL_LIMIT }; +} + +// --------------------------------------------------------------------------- +// The pull request route: a branch at the base commit, one commit made as the +// submitter, crediting everyone else, and the pull request. +// --------------------------------------------------------------------------- +// utf8Base64 encodes text as GitHub's API wants file contents: the UTF-8 +// bytes, in base64. +function utf8Base64(text) { + const bytes = new TextEncoder().encode(text); + let binary = ""; + for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); + return btoa(binary); +} + +function branchName() { + const d = new Date(); + const two = (n) => String(n).padStart(2, "0"); + let tail = ""; + for (const b of crypto.getRandomValues(new Uint8Array(6))) tail += (b % 36).toString(36); + return "abcdesign/" + d.getUTCFullYear() + two(d.getUTCMonth() + 1) + two(d.getUTCDate()) + "-" + tail; +} + +// trailerText makes a guest's display name safe for one trailer line. +function trailerText(s) { + const t = String(s || "").replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029<>]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60); + return t || "Guest"; +} + +function noreply(login, ids) { + return login + " <" + ids.get(login.toLowerCase()) + "+" + login + "@users.noreply.github.com>"; +} + +// composeTrailers credits everyone but the submitter, who is the commit's +// author: a co-author line per credited member, and for each approved guest +// change the guest's line with its approver's line directly beneath. +function composeTrailers(submitter, members, pairs, ids) { + const lines = []; + for (const login of members) { + if (login.toLowerCase() !== submitter.toLowerCase()) lines.push("Co-authored-by: " + noreply(login, ids)); + } + for (const p of pairs) lines.push("Suggested-by: " + p.guest + " (guest)", "Approved-by: " + noreply(p.approver, ids)); + return lines; +} + +// applyPassages puts some of a file's passages onto its base text. +function applyPassages(base, list) { + let text = base; + for (const p of [...list].sort((a, b) => b.baseStart - a.baseStart)) { + text = text.slice(0, p.baseStart) + p.after + text.slice(p.baseEnd); + } + return text; +} + +async function ghSend(method, path, body) { + let res; + try { + res = await fetch(GITHUB_API + path, { + method, + headers: { Accept: "application/vnd.github+json", "Content-Type": "application/json", Authorization: "Bearer " + githubToken }, + body: body === undefined ? undefined : JSON.stringify(body), + credentials: "omit", cache: "no-store", referrerPolicy: "no-referrer", + }); + } catch { + return { status: 0, json: null }; + } + const json = await res.json().catch(() => null); + return { status: res.status, json }; +} + +// GitHubTrouble is a failed step, already in words the person can act on. +class GitHubTrouble extends Error {} + +function trouble(step, status, errors) { + const types = Array.isArray(errors) ? errors.map((e) => (e && typeof e.type === "string" ? e.type : "")) : []; + if (status === 0) return new GitHubTrouble(WORDS.ghUnreachable); + if (status === 401) return new GitHubTrouble(WORDS.ghSignIn); + if (status === 403 || types.includes("FORBIDDEN") || types.includes("INSUFFICIENT_SCOPES")) return new GitHubTrouble(WORDS.ghScope); + if (status === 404 || types.includes("NOT_FOUND")) return new GitHubTrouble(WORDS.ghNotFound); + if (step === "commit" && (status === 422 || types.includes("STALE_DATA"))) return new GitHubTrouble(WORDS.ghStale); + if (step === "ref" && status === 422) return new GitHubTrouble(WORDS.ghRefTaken); + return new GitHubTrouble(WORDS.ghOther); +} + +function prBody(list, members, pairs, changed) { + const people = new Set(members.map((l) => "@" + l)); + for (const p of pairs) people.add(p.guest + " (guest)"); + for (const r of session.roster) { + if (r.role === "member" && typeof r.login === "string" && NAME_RE.test(r.login)) people.add("@" + r.login); + else if (r.role !== "member" && typeof r.name === "string" && r.name) people.add(trailerText(r.name) + " (guest)"); + } + const out = [ + "Edits to the brief made together in a live session, from base commit " + meta.get("base_sha") + ".", + "", + "Participants: " + [...people].join(", "), + "", + "Changed passages:", + ]; + for (const p of list) out.push("- `" + relPath(p.path) + "`, " + linesLabel(p) + ": " + creditLines(p).join("; ")); + if (pairs.length) { + out.push("", "Approved guest suggestions:"); + for (const p of pairs) out.push("- " + p.guest + " (guest), approved by @" + p.approver); + } + out.push("", "Changed files:", ...changed.map((f) => "- `" + f + "`")); + return out.join("\n"); +} + +let sending = false; +// openPullRequest sends the session as one pull request: every passage, or +// (one too long for an issue) the single passage shown, read afresh so what +// is sent is what the panel shows. +async function openPullRequest(only) { + if (sending) return; + const analysis = analyse(); + const all = analysis.flatMap((f) => f.passages); + const list = only ? all.filter((p) => p.path === only.path && p.before === only.before && p.after === only.after) : all; + if (list.length === 0) { + showSendResult(only ? WORDS.passageMoved : WORDS.sendNothing, true); + renderSend(); + return; + } + if (!githubToken || session.role !== "member" || !session.login) { showSendResult(WORDS.prNeedsSignIn, true); return; } + const baseSha = meta.get("base_sha"); + const target = meta.get("branch"); + if (!SHA_RE.test(baseSha || "") || typeof target !== "string" || !target) { showSendResult(WORDS.prNoBase, true); return; } + sending = true; + renderSend(); + showSendResult(WORDS.prWorking, false); + let branch = ""; + let refMade = false; + try { + // The files: each changed file with these passages put on its base text. + const additions = []; + const changed = []; + for (const f of analysis) { + const mine = list.filter((p) => p.path === f.path); + if (mine.length === 0) continue; + const text = mine.length === f.passages.length ? f.current : applyPassages(f.base, mine); + if (text === f.base) continue; + additions.push({ path: f.path, contents: utf8Base64(text) }); + changed.push(f.path); + } + if (additions.length === 0) { showSendResult(WORDS.sendNothing, true); return; } + + // Who to credit, and their GitHub ids for the noreply addresses. + const credited = new Map(); + const pairs = new Map(); + for (const p of list) { + for (const l of p.authors) credited.set(l.toLowerCase(), l); + for (const a of p.approvals) { + if (!NAME_RE.test(a.approver)) continue; + credited.set(a.approver.toLowerCase(), a.approver); + const guest = trailerText(a.guest); + pairs.set(guest + "\u0000" + a.approver.toLowerCase(), { guest, approver: a.approver }); + } + } + const members = [...credited.values()].sort((a, b) => a.localeCompare(b)); + const ids = new Map(); + for (const login of members) { + if (login.toLowerCase() === session.login.toLowerCase() && ![...pairs.values()].some((p) => p.approver.toLowerCase() === login.toLowerCase())) continue; + const r = await ghSend("GET", "/users/" + encodeURIComponent(login)); + if (r.status !== 200 || !r.json || !Number.isSafeInteger(r.json.id)) throw trouble("users", r.status); + ids.set(login.toLowerCase(), r.json.id); + } + const trailers = composeTrailers(session.login, members, [...pairs.values()], ids); + + // 1. The branch, at the commit the session started from. + branch = branchName(); + const ref = await ghSend("POST", repoPath() + "/git/refs", { ref: "refs/heads/" + branch, sha: baseSha }); + if (ref.status !== 201) throw trouble("ref", ref.status); + refMade = true; + + // 2. One commit on it, made as the submitter. + const message = { headline: COMMIT_HEADLINE, body: "Changed files:\n" + changed.map((f) => "- " + f).join("\n") + (trailers.length ? "\n\n" + trailers.join("\n") : "") }; + const commit = await ghSend("POST", "/graphql", { + query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid url } } }", + variables: { + input: { + branch: { repositoryNameWithOwner: route.slug, branchName: branch }, + message, + expectedHeadOid: baseSha, + fileChanges: { additions }, + }, + }, + }); + const made = commit.json && commit.json.data && commit.json.data.createCommitOnBranch && commit.json.data.createCommitOnBranch.commit; + if (commit.status !== 200 || !made || (commit.json.errors && commit.json.errors.length)) { + throw trouble("commit", commit.status === 200 && !made ? 422 : commit.status, commit.json && commit.json.errors); + } + + // 3. The pull request into the branch the session read. + const pr = await ghSend("POST", repoPath() + "/pulls", { + title: COMMIT_HEADLINE, head: branch, base: target, body: prBody(list, members, [...pairs.values()], changed), + }); + if (pr.status !== 201 || !pr.json || typeof pr.json.html_url !== "string") { + refMade = false; // the commit is worth keeping: the person can open the pull request by hand + throw new GitHubTrouble(WORDS.ghPRRefused + " " + branch + "."); + } + refMade = false; + showSendResult(WORDS.prOpened, false, pr.json.html_url); + } catch (err) { + if (refMade) { + // The branch holds nothing yet: take it away again. + await ghSend("DELETE", repoPath() + "/git/refs/heads/" + branch.split("/").map(encodeURIComponent).join("/")); + } + showSendResult(err instanceof GitHubTrouble ? err.message : WORDS.ghOther, true); + } finally { + sending = false; + renderSend(); + } +} + +// --------------------------------------------------------------------------- +// The "Send to GitHub" panel: the passages, their credit and the route's +// actions. It reads meta.mode, the approvals, the base snapshot and the +// roster's credits. // --------------------------------------------------------------------------- function submitSession() { - toast(WORDS.submitComingNext); + ui.send.hidden = !ui.send.hidden; + if (!ui.send.hidden) { + showSendResult("", false); + renderSend(); + } } +function showSendResult(text, bad, href) { + clear(ui.sendResult); + ui.sendResult.className = bad ? "state bad" : "state"; + if (text) ui.sendResult.append(text); + if (href) { + let ok = false; + try { ok = new URL(href).origin === new URL(GITHUB_WEB).origin; } catch { ok = false; } + ui.sendResult.append(" ", ok ? el("a", { href, target: "_blank", rel: "noopener noreferrer", text: href }) : href); + } +} + +let sendTimer = 0; +doc.on("update", () => { + if (!ui.send || ui.send.hidden) return; + clearTimeout(sendTimer); + sendTimer = setTimeout(() => renderSend(), 600); +}); + +function renderSend() { + if (!ui.send || ui.send.hidden) return; + if (session.role !== "member" || session.over) { ui.send.hidden = true; return; } + const mode = meta.get("mode") === "issues" ? "issues" : "pr"; + const analysis = analyse(); + const all = analysis.flatMap((f) => f.passages); + ui.sendIntro.textContent = mode === "issues" ? WORDS.sendIntroIssues : WORDS.sendIntroPR; + clear(ui.sendList); + clear(ui.sendActions); + if (all.length === 0) { + ui.sendList.append(el("li", { class: "state", text: WORDS.sendNothing })); + } + for (const p of all) { + const actions = el("div", { class: "row" }); + if (mode === "issues") { + const link = issueLink(p); + if (link.fits) { + actions.append(el("a", { class: "button", href: link.url, target: "_blank", rel: "noopener noreferrer", text: WORDS.sendAsIssue })); + } else { + actions.append(el("span", { class: "state", text: WORDS.tooLongForIssue }), + el("button", { type: "button", disabled: sending, text: WORDS.sendAsPR, onclick: () => openPullRequest(p) })); + } + } + ui.sendList.append(el("li", { class: "passage" }, + el("div", { class: "from", text: relPath(p.path) + " · " + linesLabel(p) }), + el("div", { class: "credit", text: creditLines(p).join("\n") }), + el("del", { text: p.before }), + el("ins", { text: p.after }), + actions.childElementCount ? actions : null)); + } + if (mode === "pr") { + ui.sendActions.append(el("button", { + type: "button", class: "primary", disabled: sending || all.length === 0 || !githubToken, + text: WORDS.openPR, onclick: () => openPullRequest(null), + })); + if (!githubToken) ui.sendActions.append(el("span", { class: "state", text: WORDS.prNeedsSignIn })); + } + ui.sendActions.append( + el("button", { type: "button", text: WORDS.sendRefresh, onclick: () => renderSend() }), + el("button", { type: "button", text: WORDS.sendClose, onclick: () => { ui.send.hidden = true; } })); +} + + // --------------------------------------------------------------------------- // The editor. // --------------------------------------------------------------------------- @@ -1054,8 +1616,14 @@ function build() { ui.guestbar = el("div", { class: "guestbar", hidden: true }, el("p", { class: "state", text: WORDS.guestHint }), el("div", { class: "row" }, ui.suggestButton), ui.suggestForm); ui.side = el("aside", { class: "side" }); + ui.sendIntro = el("p", { class: "state" }); + ui.sendList = el("ul"); + ui.sendActions = el("div", { class: "row" }); + ui.sendResult = el("p", { class: "state", role: "status", "aria-live": "polite" }); + ui.send = el("section", { class: "send", hidden: true, "aria-label": WORDS.sendHeading }, + el("h2", { text: WORDS.sendHeading }), ui.sendIntro, ui.sendList, ui.sendActions, ui.sendResult); ui.work = el("div", { class: "work", hidden: true }, - ui.files, el("section", { class: "center" }, ui.filetitle, ui.editor, ui.guestbar), ui.side); + ui.files, el("section", { class: "center" }, ui.send, ui.filetitle, ui.editor, ui.guestbar), ui.side); app.append(bar, ui.start, ui.work, ui.toast); return true; @@ -1233,6 +1801,7 @@ async function copyLink(url) { } function renderAll() { + if (session.over && ui.send) ui.send.hidden = true; renderTop(); renderStart(); renderSide(); diff --git a/internal/surface/session/web/web_test.go b/internal/surface/session/web/web_test.go index 00474e3caf..ec5f36e724 100644 --- a/internal/surface/session/web/web_test.go +++ b/internal/surface/session/web/web_test.go @@ -343,10 +343,11 @@ func TestPageScriptKeepsTheTokenAndTheDOMSafe(t *testing.T) { if len(imports) != 1 || imports[0][1] != "./vendor/collab.js" { t.Errorf("session.js imports %v, want ./vendor/collab.js alone", imports) } - // The only addresses it names are GitHub's API and sign-in page. + // The only addresses it names are GitHub's API, its sign-in page and its + // site, where a pre-filled issue opens. urls := regexp.MustCompile(`(?i)\b(?:https?|wss?)://[^\s"'`+"`"+`)]*`).FindAllString(js, -1) for _, u := range urls { - if u != "https://api.github.com" && u != "https://github.com/login/oauth/authorize" && u != "https://" { + if u != "https://api.github.com" && u != "https://github.com/login/oauth/authorize" && u != "https://github.com" && u != "https://" { t.Errorf("session.js names %q", u) } } @@ -418,6 +419,107 @@ func functionBody(t *testing.T, js, name string) string { return rest[:end+2] } +func TestIssueLinksAreMeasuredAgainstOneNamedLimit(t *testing.T) { + js := pageScript(t) + m := regexp.MustCompile(`(?m)((?://.*\n)+)const ISSUE_URL_LIMIT = (\d+);`).FindStringSubmatch(js) + if m == nil { + t.Fatal("session.js has no ISSUE_URL_LIMIT constant with a comment above it") + } + var limit int + if _, err := fmt.Sscan(m[2], &limit); err != nil || limit > 8000 || limit < 4000 { + t.Errorf("ISSUE_URL_LIMIT = %s, want a margin under GitHub's roughly 8 KB", m[2]) + } + if !strings.Contains(m[1], "2026-10-14-github-roles-admission-and-issue-links.md") { + t.Errorf("the limit's comment does not cite the research note:\n%s", m[1]) + } + // The link is built with encodeURIComponent and measured in bytes, whole. + link := functionBody(t, js, "issueLink") + for _, want := range []string{`"/issues/new?title=" + encodeURIComponent(`, `"&body=" + encodeURIComponent(`, "utf8Length(url) <= ISSUE_URL_LIMIT"} { + if !strings.Contains(link, want) { + t.Errorf("issueLink does not carry %q", want) + } + } + // No labels in the link: GitHub answers 404 to a visitor who may not set them. + for _, bad := range []string{"labels=", "&labels", `"labels"`, "labels:"} { + if strings.Contains(js, bad) { + t.Errorf("session.js carries %q", bad) + } + } + // The issue body names the file, the base commit, both versions and the credit. + body := functionBody(t, js, "issueBody") + for _, want := range []string{"p.path", `meta.get("base_sha")`, "fenced(p.before)", "fenced(p.after)", "creditLines(p)"} { + if !strings.Contains(body, want) { + t.Errorf("issueBody does not carry %s", want) + } + } + credit := functionBody(t, js, "creditLines") + if !strings.Contains(credit, `"Suggested by " + a.guest + " (guest), approved by @" + a.approver`) { + t.Error("creditLines does not credit a guest's change to its approver") + } +} + +func TestPullRequestCommitCreditsEveryone(t *testing.T) { + js := pageScript(t) + trailers := functionBody(t, js, "composeTrailers") + for _, want := range []string{ + `"Co-authored-by: " + noreply(`, + `lines.push("Suggested-by: " + p.guest + " (guest)", "Approved-by: " + noreply(`, + "!== submitter.toLowerCase()", + } { + if !strings.Contains(trailers, want) { + t.Errorf("composeTrailers does not carry %q", want) + } + } + if !strings.Contains(functionBody(t, js, "noreply"), `"+" + login + "@users.noreply.github.com>"`) { + t.Error("noreply does not use GitHub's id+login noreply address") + } + pr := functionBody(t, js, "openPullRequest") + for _, want := range []string{ + `"/git/refs"`, `ref: "refs/heads/" + branch, sha: baseSha`, + `"/graphql"`, "createCommitOnBranch", "expectedHeadOid: baseSha", "repositoryNameWithOwner", + `"/pulls"`, "head: branch, base: target", + `"/users/" + encodeURIComponent(`, + "headline: COMMIT_HEADLINE", + } { + if !strings.Contains(pr, want) { + t.Errorf("openPullRequest does not carry %q", want) + } + } + if !strings.Contains(js, `const COMMIT_HEADLINE = "Edit the brief in a live session";`) { + t.Error("the commit headline is not the spec's") + } + if !regexp.MustCompile(`"abcdesign/" \+ `).MatchString(functionBody(t, js, "branchName")) { + t.Error("branchName does not name an abcdesign/ branch") + } +} + +func TestFileContentsAreUTF8Base64(t *testing.T) { + js := pageScript(t) + enc := functionBody(t, js, "utf8Base64") + if !strings.Contains(enc, "new TextEncoder().encode(text)") || !strings.Contains(enc, "btoa(binary)") { + t.Errorf("utf8Base64 does not encode UTF-8 bytes:\n%s", enc) + } + if !strings.Contains(functionBody(t, js, "openPullRequest"), "contents: utf8Base64(") { + t.Error("the commit's file contents do not go through utf8Base64") + } + // btoa sees a binary string built from bytes, never text. + calls := regexp.MustCompile(`btoa\(([^)]*)\)`).FindAllStringSubmatch(js, -1) + if len(calls) == 0 { + t.Fatal("no btoa call found; the check is not looking") + } + for _, c := range calls { + if c[1] != "binary" && c[1] != "s" { + t.Errorf("btoa(%s): btoa is applied only to a binary string built from bytes", c[1]) + } + } + for _, fn := range []string{"b64url", "utf8Base64"} { + body := functionBody(t, js, fn) + if !strings.Contains(body, "String.fromCharCode(") { + t.Errorf("%s does not build its binary string from bytes", fn) + } + } +} + func TestSuggestionsLiveInTheDocument(t *testing.T) { js := pageScript(t) if !strings.Contains(js, `const suggestions = doc.getMap("suggestions");`) { From de009870a9c0969065ed968a4185b62854157765 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:45:49 +0100 Subject: [PATCH 032/165] docs: map the session packages and say how to serve the session page The package map gains core/session, surface/session and surface/session/web. The relay how-to gains a short section on writing the page with `abcd session page --out` and publishing it on any static host whose 404.html fallback reaches ///brief/. Assisted-by: Claude:claude-opus-5-5 --- docs/how-to/run-a-brief-session-relay.md | 23 +++++++++++++++++++++++ internal/README.md | 17 +++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/docs/how-to/run-a-brief-session-relay.md b/docs/how-to/run-a-brief-session-relay.md index 1bf1ffa0a8..ce7b549a16 100644 --- a/docs/how-to/run-a-brief-session-relay.md +++ b/docs/how-to/run-a-brief-session-relay.md @@ -96,6 +96,29 @@ keeps neither the token nor anything else once it has the answer. Keeping the relay and its secret safe is the team's responsibility. +## Serve the session page + +The page everyone opens is a few plain files that abcd writes for you to put +on a static host of your choice. + +1. Write them into an empty directory: + + ```bash + abcd session page --out site + ``` + + The command writes `index.html`, an identical `404.html`, the page's script + and stylesheet, and the editor bundle under `vendor/`. It refuses a + directory that holds anything else. + +2. Publish that directory on any static host, such as GitHub Pages. The host + must answer an unknown path with `404.html`: that is how + `///brief/` reaches the page. + +3. Open `https://///brief/` and give the page your + relay's https address. If you set up a sign-in app, its callback URL is + your site's address. + ## Who can join - Anyone the host gives the join code to joins as a guest. A guest sees the diff --git a/internal/README.md b/internal/README.md index 53822cd202..a5618cf8b3 100644 --- a/internal/README.md +++ b/internal/README.md @@ -93,6 +93,12 @@ plugin surface, and a future MCP server share one engine. over them. History importing either assembler to learn the grammar would pull the assemblers into the transcript store, so the grammar lives here and each of them imports it. +- **`core/session/`** — the brief session relay's engine + (spc-2610141709430146): rooms, join codes, the member and guest roles, each + room's update log held in memory only, guests' suggestions, the Yjs client + ids credited to members, the limits and rates, and `blueprint`, which writes + the team's own relay deployment under `.abcd/relay/`. It never parses the + brief and never writes to disk; `surface/session` carries it over the wire. - **`core/relink/`** — the one link-repoint primitive. A record's folder is its status, so every lifecycle transition is a rename, and a rename strands every relative link that named the file where it was. The verbs that move a record @@ -341,6 +347,17 @@ plugin surface, and a future MCP server share one engine. width and a colour rung, and the sanitising every part passes first. The width comes from `term.Size`; text is measured and wrapped through `textwidth`. +- **`surface/session/`** — the relay's HTTP front door, behind `abcd session + serve`: the WebSocket at `/session` speaking `core/session`'s messages, the + sign-in exchange at `/auth/token` that adds the team's secret, `/config`, + `/healthz`, and the origin and forwarded-address checks. +- **`surface/session/web/`** — the static session page `abcd session page + --out` writes, embedded in the binary: the HTML, the page script, the + stylesheet and the vendored, provenance-pinned editor bundle. The page joins + a relay, edits the brief live, and sends the session back to GitHub as + pre-filled issues or one pull request with the person's own token. No Node + toolchain enters the repository (adr-47), so its tests are Go tests that pin + the script's structure. - **`surface/mcp/`** *(later)* — an additive front door exposing the same core verbs as `mcp:abcd:*` tools. Added once a surface is worth exposing; no core rework required because the core is transport-agnostic. From 7b60f27eec4f031a8638f23d7a39edb04acd5ae3 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:45:50 +0100 Subject: [PATCH 033/165] docs: mark the brief session spec's back-to-GitHub step landed Assisted-by: Claude:claude-opus-5-5 --- ...spc-2610141709430146-the-product-thinker-and-the-technical.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md index f3b587ad97..02be0b2ca2 100644 --- a/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md +++ b/.abcd/development/specs/open/spc-2610141709430146-the-product-thinker-and-the-technical.md @@ -92,3 +92,4 @@ The hosting of abcdesign.app is recorded in the plan but not made in this change 4. Back to GitHub: issue links under the measured length limit, and one pull request with co-author, Suggested-by and Approved-by lines - packages: internal/surface/session/web - tests: internal/surface/session + - landed: a30ec9432 From 90586c67bc49612b4b4efd8f36ad2ea90e17b2b6 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:46:36 +0100 Subject: [PATCH 034/165] fix: publish a project site only while public and switched on at head Visibility was checked only at setup, so a repository made private later kept publishing its development record, and Pages from a private repository is public on every plan but Enterprise. The switch was read only at the released tag, so a later push or dispatch rendering an older tag undid a switch turned off on the default branch. The switch step now reads the event's repository visibility through an env var and the switch at the head of the default branch as well as at the tag; a missing or malformed file reads as off. The deploy job also requires toJSON(github.event.repository.private) == 'false', compared as JSON because a loose == false reads a missing field as public. The step is run with bash against a real checkout in a test, and the Pages workflow is pinned in a golden file. Assisted-by: Claude:claude-opus-5-5 --- internal/core/site/setup_pages_switch_test.go | 164 ++++++++++++ internal/core/site/setup_pages_test.go | 2 +- internal/core/site/setupsrc/site.yml.tmpl | 39 ++- .../testdata/site-workflow-github-pages.yml | 249 ++++++++++++++++++ 4 files changed, 443 insertions(+), 11 deletions(-) create mode 100644 internal/core/site/setup_pages_switch_test.go create mode 100644 internal/core/site/testdata/site-workflow-github-pages.yml diff --git a/internal/core/site/setup_pages_switch_test.go b/internal/core/site/setup_pages_switch_test.go new file mode 100644 index 0000000000..124555992b --- /dev/null +++ b/internal/core/site/setup_pages_switch_test.go @@ -0,0 +1,164 @@ +package site + +// The project-site switch as the release workflow reads it +// (itd-2610091010348892): the switch step setup writes is run with bash in a +// checkout shaped the way actions/checkout leaves one (the released tag +// checked out, fetch-depth 0, so every branch is there as origin/), +// and its output decides whether the deploy job runs at all. Nothing here +// reaches a network. + +import ( + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/intentdriven/abcd/internal/adapter/hosting" + "github.com/intentdriven/abcd/internal/adapter/hosting/githubpages" + "github.com/intentdriven/abcd/internal/gittest" +) + +// switchStep is the run block of the workflow's switch step, unindented. +func switchStep(t *testing.T, wf string) string { + t.Helper() + lines := strings.Split(wf, "\n") + start := -1 + for i, l := range lines { + if strings.TrimSpace(l) == "id: switch" { + start = i + break + } + } + if start < 0 { + t.Fatal("the workflow has no switch step") + } + var script []string + runIndent := -1 + for _, l := range lines[start+1:] { + indent := len(l) - len(strings.TrimLeft(l, " ")) + if runIndent < 0 { + if strings.TrimSpace(l) == "run: |" { + runIndent = indent + } + continue + } + if strings.TrimSpace(l) != "" && indent <= runIndent { + break + } + script = append(script, strings.TrimPrefix(l, strings.Repeat(" ", runIndent+2))) + } + if len(script) == 0 { + t.Fatal("the switch step has no run block") + } + return strings.Join(script, "\n") + "\n" +} + +func TestTheSwitchIsReadAtTheTagAndTheDefaultBranchAndOnlyWhilePublic(t *testing.T) { + if _, err := exec.LookPath("jq"); err != nil { + t.Skip("jq is not installed; the workflow's runner carries it") + } + if _, err := exec.LookPath("bash"); err != nil { + t.Skip("bash is not installed") + } + wf, err := renderSiteWorkflow("trunk", githubpages.Adapter{}, hosting.Site{Name: "widgets", Repo: "acme/widgets"}) + if err != nil { + t.Fatal(err) + } + script := switchStep(t, string(wf)) + + const ( + on = `{"hosting": {"provider": "github-pages", "name": "widgets", "publish": true}}` + off = `{"hosting": {"provider": "github-pages", "name": "widgets", "publish": false}}` + malformed = `{"hosting": {"publish": true` + missing = "" + unset = "" + ) + for name, c := range map[string]struct { + atTag, atHead, private, want string + }{ + "on at the tag and the head, public": {on, on, "false", "true"}, + "switched off on the default branch since": {on, off, "false", "false"}, + "no switch file on the default branch": {on, missing, "false", "false"}, + "a malformed switch file on the branch": {on, malformed, "false", "false"}, + "off at the tag, on at the head": {off, on, "false", "false"}, + "made private since setup": {on, on, "true", "false"}, + "a visibility the event does not carry": {on, on, "", "false"}, + "a visibility the step was handed no value of": {on, on, unset, "false"}, + } { + t.Run(name, func(t *testing.T) { + origin := gittest.NewRepo(t) + origin.Git("checkout", "-q", "-b", "trunk") + origin.Write(ManifestRelPath, c.atTag) + origin.Commit("the released commit") + origin.Git("tag", "v1.0.0") + if c.atHead == missing { + origin.Remove(ManifestRelPath) + } else { + origin.Write(ManifestRelPath, c.atHead) + } + origin.Commit("the default branch since the release") + + clone := filepath.Join(t.TempDir(), "checkout") + origin.Git("clone", "-q", "--no-checkout", origin.Root(), clone) + git := func(args ...string) { + t.Helper() + cmd := exec.Command("git", append([]string{"-C", clone, "-c", "gc.auto=0", "-c", "maintenance.auto=false"}, args...)...) + cmd.Env = origin.Env() + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + } + git("checkout", "-q", "--detach", "v1.0.0") + + output := filepath.Join(t.TempDir(), "github_output") + if err := os.WriteFile(output, nil, 0o644); err != nil { + t.Fatal(err) + } + cmd := exec.Command("bash", "-c", script) + cmd.Dir = clone + cmd.Env = append(origin.Env(), "GITHUB_OUTPUT="+output) + if c.private != unset { + cmd.Env = append(cmd.Env, "REPO_PRIVATE="+c.private) + } + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("the switch step failed: %v\n%s", err, out) + } + raw, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + if got := strings.TrimSpace(string(raw)); got != "on="+c.want { + t.Fatalf("the switch step wrote %q, want on=%s", got, c.want) + } + }) + } +} + +// TestTheDeployJobRunsOnlyForAPublicRepository pins the deploy job's gate: +// the switch the render job read, and the repository's visibility as the +// event carries it, compared as JSON so a missing field never reads as public. +func TestTheDeployJobRunsOnlyForAPublicRepository(t *testing.T) { + wf, err := renderSiteWorkflow("main", githubpages.Adapter{}, hosting.Site{Name: "widgets", Repo: "acme/widgets"}) + if err != nil { + t.Fatal(err) + } + gate := regexp.MustCompile(`(?m)^ deploy:\n needs: render\n if: needs\.render\.outputs\.publish == 'true' && toJSON\(github\.event\.repository\.private\) == 'false'\n`) + if !gate.Match(wf) { + t.Error("the deploy job is not gated on the switch and on a public repository") + } + if !strings.Contains(string(wf), " REPO_PRIVATE: ${{ github.event.repository.private }}\n") { + t.Error("the switch step is not handed the repository's visibility") + } +} + +// TestThePagesWorkflowIsPinned pins the workflow setup writes for the +// github-pages provider byte for byte. +func TestThePagesWorkflowIsPinned(t *testing.T) { + wf, err := renderSiteWorkflow("main", githubpages.Adapter{}, hosting.Site{Name: "widgets", Repo: "acme/widgets"}) + if err != nil { + t.Fatal(err) + } + golden(t, "site-workflow-github-pages.yml", wf) +} diff --git a/internal/core/site/setup_pages_test.go b/internal/core/site/setup_pages_test.go index 5d60b55eb5..f104d747b9 100644 --- a/internal/core/site/setup_pages_test.go +++ b/internal/core/site/setup_pages_test.go @@ -304,7 +304,7 @@ func TestThePagesWorkflowHoldsNoSecretAndInterpolatesNothingIntoAShell(t *testin if strings.Contains(s, "for the host configuration") { t.Error("the Pages deploy job checks out the repository, which it reads nothing from") } - if !regexp.MustCompile(`(?m)^ deploy:\n needs: render\n if: needs.render.outputs.publish == 'true'\n`).MatchString(s) { + if !regexp.MustCompile(`(?m)^ deploy:\n needs: render\n if: needs.render.outputs.publish == 'true' && `).MatchString(s) { t.Error("the deploy job is not gated on the switch") } if got := strings.Count(s, " if: steps.switch.outputs.on == 'true'\n"); got != 5 { diff --git a/internal/core/site/setupsrc/site.yml.tmpl b/internal/core/site/setupsrc/site.yml.tmpl index b1b2bf7ebd..20ef9e05e9 100644 --- a/internal/core/site/setupsrc/site.yml.tmpl +++ b/internal/core/site/setupsrc/site.yml.tmpl @@ -13,9 +13,10 @@ name: site # short-lived token and runs nothing from the repository. <% end %><% if .Switched %># # THE SWITCH: the site publishes only while `"publish": true` stands in the -# hosting block of <% .SwitchFile %> at the released commit. Switched off, a -# release builds and publishes nothing, and `abcd site setup` offers to -# remove this file. +# hosting block of <% .SwitchFile %> both at the released commit and at the +# head of <% .DefaultBranch %>, and only while the repository is public. +# Switched off, a release builds and publishes nothing, a later push or +# dispatch included, and `abcd site setup` offers to remove this file. <% end %># # Three entry points, one path: # @@ -126,16 +127,32 @@ jobs: fetch-depth: 0 persist-credentials: false -<% if .Switched %> - name: Read the project-site switch at the released commit +<% if .Switched %> # On only while all three hold: the repository is public (Pages from a + # private repository is public on every plan but Enterprise), and the + # switch is on both at the released commit and at the head of + # <% .DefaultBranch %>, so a switch turned off there is not undone by a + # later push or dispatch that renders an older tag. A missing or + # malformed file reads as off. + - name: Read the project-site switch id: switch + env: + REPO_PRIVATE: ${{ github.event.repository.private }} run: | set -euo pipefail - if jq -e '.hosting.publish == true' <% .SwitchFile %> >/dev/null 2>&1; then - echo "on=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::the project site is switched off in <% .SwitchFile %>, so this release publishes nothing" - echo "on=false" >> "$GITHUB_OUTPUT" + on=true + if [ "${REPO_PRIVATE:-}" != false ]; then + echo "::notice::this repository is not public, so this release publishes nothing" + on=false + fi + if ! jq -e '.hosting.publish == true' <% .SwitchFile %> >/dev/null 2>&1; then + echo "::notice::the project site is switched off in <% .SwitchFile %> at the released commit, so this release publishes nothing" + on=false + fi + if ! git show "origin/<% .DefaultBranch %>:<% .SwitchFile %>" 2>/dev/null | jq -e '.hosting.publish == true' >/dev/null 2>&1; then + echo "::notice::the project site is switched off in <% .SwitchFile %> on <% .DefaultBranch %>, so this release publishes nothing" + on=false fi + echo "on=$on" >> "$GITHUB_OUTPUT" <% end %> - name: Download abcd and its checksums <% .IfOn %> env: @@ -177,7 +194,9 @@ jobs: deploy: needs: render -<% if .Switched %> if: needs.render.outputs.publish == 'true' +<% if .Switched %> if: needs.render.outputs.publish == 'true' && toJSON(github.event.repository.private) == 'false' + # The visibility is compared as JSON: a loose `== false` reads a missing + # field as false, which would read as public. <% end %> runs-on: ubuntu-latest timeout-minutes: 15 environment: <% .DeployEnv %> diff --git a/internal/core/site/testdata/site-workflow-github-pages.yml b/internal/core/site/testdata/site-workflow-github-pages.yml new file mode 100644 index 0000000000..8cfba8da6f --- /dev/null +++ b/internal/core/site/testdata/site-workflow-github-pages.yml @@ -0,0 +1,249 @@ +name: site + +# Written by `abcd site setup` (itd-2609061543533170). Re-running the verb +# rewrites nothing while this file is unchanged, and refuses a hand edit unless +# it is told to replace it. +# +# Renders this repository's site from each published release with abcd's own +# released binary — checksum- and attestation-verified before it runs — and +# deploys the rendered artefact to github-pages. The render job holds no +# credential, and neither does the deploy job: it deploys with the run's own +# short-lived token and runs nothing from the repository. +# +# THE SWITCH: the site publishes only while `"publish": true` stands in the +# hosting block of .abcd/site.json both at the released commit and at the +# head of main, and only while the repository is public. +# Switched off, a release builds and publishes nothing, a later push or +# dispatch included, and `abcd site setup` offers to remove this file. +# +# Three entry points, one path: +# +# release: published a release published by a person or a personal token. +# workflow_run the `release` or `auto-release` workflow finishing on +# main or on a v-tag. A release created with +# the workflow's own GITHUB_TOKEN fires no `release` +# event, so a release workflow's completion is what +# carries those releases here: `auto-release` runs on +# every push to main and calls `release` inside +# its own run, and a hand-pushed tag runs `release` with +# the tag as its head branch. A completion that +# released nothing redeploys the latest published +# release: the same bytes the site already serves, +# unless a dispatch rolled it back to an older tag, +# which the next such completion replaces with the +# latest release again. +# workflow_dispatch a redeploy by hand; an empty tag means the latest +# published release. +# +# THE ENVIRONMENT CONTRACT (the tree's record of the forge's state; `abcd site +# setup` creates both through the forge's API when it is asked to): +# +# site-render the render job. No secrets. Deployment policy: branch +# `main` and tags `v*`. +# github-pages the deploy job. Same policy. No secrets: the run's own token. +# +# The policy refuses unreviewed workflow content: a dispatch runs this file as +# it exists on the ref it was dispatched from, so a branch that is neither the +# default branch nor a release tag is refused before either job starts. +on: + release: + types: [published] + workflow_run: + workflows: [release, auto-release] + types: [completed] + workflow_dispatch: + inputs: + tag: + description: Release tag to render and deploy (vX.Y.Z). Empty means the latest published release. + required: false + type: string + default: '' + +# One production target, so one queue. A deploy cancelled between uploading +# assets and switching the route leaves the site half-moved. +concurrency: + group: site-production + cancel-in-progress: false + +permissions: + contents: read + +jobs: + render: + # A workflow_run fires for the release workflows' runs whatever triggered + # them, and a head branch is a name a fork can choose. Only a successful + # run of this repository's own release workflows, not of a pull request, + # on the default branch or a v-tag (the trigger filters no branch, because + # a tag-push run's head branch is the tag), reaches the render. Nothing + # from the triggering run is used either way: the tag is resolved from the + # repository's published releases. + if: >- + ${{ github.event_name != 'workflow_run' || ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event != 'pull_request' && + github.event.workflow_run.event != 'pull_request_target' && + github.event.workflow_run.head_repository.full_name == github.repository && + (github.event.workflow_run.head_branch == 'main' || + startsWith(github.event.workflow_run.head_branch, 'v'))) }} + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: site-render + permissions: + contents: read + outputs: + tag: ${{ steps.release.outputs.tag }} + publish: ${{ steps.switch.outputs.on }} + steps: + - name: Resolve the release to render + id: release + env: + GH_TOKEN: ${{ github.token }} + EVENT_TAG: ${{ github.event.release.tag_name }} + INPUT_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag="${EVENT_TAG:-${INPUT_TAG:-}}" + if [ -z "$tag" ]; then + tag="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)" + fi + if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'; then + echo "::error::the release tag is not vX.Y.Z; refusing to render it" + exit 1 + fi + state="$(gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json isPrerelease,isDraft \ + --jq 'if (.isPrerelease or .isDraft) then "true" else "false" end')" + if [ "$state" = true ]; then + echo "::error::$tag is a pre-release or a draft; the site renders published releases only" + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Check out the released commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/tags/${{ steps.release.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + # On only while all three hold: the repository is public (Pages from a + # private repository is public on every plan but Enterprise), and the + # switch is on both at the released commit and at the head of + # main, so a switch turned off there is not undone by a + # later push or dispatch that renders an older tag. A missing or + # malformed file reads as off. + - name: Read the project-site switch + id: switch + env: + REPO_PRIVATE: ${{ github.event.repository.private }} + run: | + set -euo pipefail + on=true + if [ "${REPO_PRIVATE:-}" != false ]; then + echo "::notice::this repository is not public, so this release publishes nothing" + on=false + fi + if ! jq -e '.hosting.publish == true' .abcd/site.json >/dev/null 2>&1; then + echo "::notice::the project site is switched off in .abcd/site.json at the released commit, so this release publishes nothing" + on=false + fi + if ! git show "origin/main:.abcd/site.json" 2>/dev/null | jq -e '.hosting.publish == true' >/dev/null 2>&1; then + echo "::notice::the project site is switched off in .abcd/site.json on main, so this release publishes nothing" + on=false + fi + echo "on=$on" >> "$GITHUB_OUTPUT" + + - name: Download abcd and its checksums + if: steps.switch.outputs.on == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release download --repo intentdriven/abcd \ + --pattern 'abcd-linux-amd64' --pattern 'checksums.txt' --dir "$RUNNER_TEMP" + + - name: Verify abcd against its release manifest and its provenance + if: steps.switch.outputs.on == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + cd "$RUNNER_TEMP" + grep ' abcd-linux-amd64$' checksums.txt | sha256sum -c - + gh attestation verify abcd-linux-amd64 --repo intentdriven/abcd \ + --signer-workflow intentdriven/abcd/.github/workflows/release.yml + install -m 0755 abcd-linux-amd64 "${RUNNER_TEMP}/abcd" + + - name: Render and check the site + if: steps.switch.outputs.on == 'true' + env: + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + "${RUNNER_TEMP}/abcd" site build --out site --version "${TAG#v}" --commit "$(git rev-parse --verify HEAD)" --base-path /widgets/ + "${RUNNER_TEMP}/abcd" lint site --out site --base-path /widgets/ + + - name: Pack the rendered site + if: steps.switch.outputs.on == 'true' + run: tar -czf site.tar.gz -C site . + + - name: Hand the rendered site to the deploy job + if: steps.switch.outputs.on == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: site-${{ steps.release.outputs.tag }} + path: site.tar.gz + if-no-files-found: error + retention-days: 1 + + deploy: + needs: render + if: needs.render.outputs.publish == 'true' && toJSON(github.event.repository.private) == 'false' + # The visibility is compared as JSON: a loose `== false` reads a missing + # field as false, which would read as public. + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: github-pages + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Collect the rendered site + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: site-${{ needs.render.outputs.tag }} + path: ${{ runner.temp }}/site-artifact + + # The archive is audited before a byte of it is unpacked: only relative + # paths, no parent traversal, only regular files and directories. + - name: Audit and unpack the rendered site + run: | + set -euo pipefail + archive="${RUNNER_TEMP}/site-artifact/site.tar.gz" + while IFS= read -r member; do + [ -n "$member" ] || continue + case "$member" in + /*|..|../*|*/../*|*/..) echo "::error::refusing site.tar.gz: unsafe member '$member'"; exit 1 ;; + esac + done <<< "$(tar -tzf "$archive")" + while IFS= read -r t; do + case "$t" in + -|d|'') ;; + *) echo "::error::refusing site.tar.gz: non-regular member (type '$t')"; exit 1 ;; + esac + done <<< "$(tar -tvzf "$archive" | awk '{print substr($1,1,1)}' | sort -u)" + staging="${RUNNER_TEMP}/site-unpacked" + rm -rf "$staging" site + mkdir -p "$staging" site + tar -xzf "$archive" -C "$staging" + cp -R "$staging/." site/ + test -n "$(ls -A site)" + + - name: Hand the rendered site to GitHub Pages + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: site + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 From 0efd55d4ac8137bdaa4fdf49ceb567f724f68243 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:47:13 +0100 Subject: [PATCH 035/165] fix: deny the base-path gate a pass on a page that did not parse An unparsed page never enters the page list, so the base-path gate reported ok for a page it never read. Below a base path it is now denied a pass on that page like the other page-walking gates. At the root the gate needs nothing from a page, so it is not denied there. Assisted-by: Claude:claude-opus-5-5 --- internal/core/site/basepath_test.go | 34 +++++++++++++++++++++++++++++ internal/core/site/check.go | 8 ++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/internal/core/site/basepath_test.go b/internal/core/site/basepath_test.go index 3923bf8786..5e5e23a66a 100644 --- a/internal/core/site/basepath_test.go +++ b/internal/core/site/basepath_test.go @@ -195,3 +195,37 @@ func hasFinding(res CheckResult, check, detail string) bool { } return false } + +// TestTheBasePathGateIsDeniedAPassOnAnUnparsedPage: a page that does not parse +// is never walked, so the base-path gate cannot report ok for it; a link that +// leaves the site on that page would otherwise disappear behind the parse +// fault. +func TestTheBasePathGateIsDeniedAPassOnAnUnparsedPage(t *testing.T) { + f := newFixture(t) + out, _ := buildFixtureAt(t, f, "/widgets/") + page := filepath.Join(out, "index.html") + raw, err := os.ReadFile(page) + if err != nil { + t.Fatal(err) + } + broken := bytes.Replace(raw, []byte(`