diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 37ee268d9..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# Build and CI infrastructure -/.github/workflows/ @kasbah @jmoggr -/infrastructure/ @kasbah @jmoggr -*.nix @kasbah @jmoggr - -# Components and styling -*.css @kasbah -/packages/ui-components/ @kasbah diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8fbfa9d43..f28b2c15d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Let us know if something is broken, or not working how you expect it to title: '' -labels: bug +type: bug assignees: '' --- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d34b6c53..d53d47ac9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -227,6 +227,11 @@ jobs: run: | tlmgr update --self tlmgr install dvisvgm standalone preview pgf tikz-cd amsmath quiver spath3 ebproof + # Work around https://github.com/loopspace/spath3/issues/37 by + # dropping the redundant `NNn` variant that newer expl3 rejects. + SPATH3="$(kpsewhich spath3.sty)" + sed -i 's|\\spath_maybe_split_curve:NNn {NNn, NNV }|\\spath_maybe_split_curve:NNn {NNV}|' "$SPATH3" + sed -i 's|\\spath_maybe_gsplit_curve:NNn {NNn, NNV}|\\spath_maybe_gsplit_curve:NNn {NNV}|' "$SPATH3" - name: Build mathematical docs if: steps.cache-math-docs.outputs.cache-hit != 'true' @@ -287,12 +292,18 @@ jobs: run: | tlmgr update --self tlmgr install dvisvgm standalone preview pgf tikz-cd amsmath quiver spath3 ebproof luatex85 + # Work around https://github.com/loopspace/spath3/issues/37 by + # dropping the redundant `NNn` variant that newer expl3 rejects. + SPATH3="$(kpsewhich spath3.sty)" + sed -i 's|\\spath_maybe_split_curve:NNn {NNn, NNV }|\\spath_maybe_split_curve:NNn {NNV}|' "$SPATH3" + sed -i 's|\\spath_maybe_gsplit_curve:NNn {NNn, NNV}|\\spath_maybe_gsplit_curve:NNn {NNV}|' "$SPATH3" - name: Render Quarto project if: steps.cache-rfc.outputs.cache-hit != 'true' uses: quarto-dev/quarto-actions/render@v2 with: path: rfc + to: html - name: Report cache status run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15dbf1b93..0f1402532 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,31 +81,6 @@ jobs: run: | nix build .#rust-docs-check - ui_components_tests: - name: ui-components tests - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup NodeJS - uses: actions/setup-node@v4 - with: - node-version: 24 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install - - - name: Install Playwright browser - run: pnpm --filter ./packages/ui-components exec playwright install chromium - - - name: Run ui-components tests - run: pnpm --filter ./packages/ui-components run test - npm_checks: name: npm checks runs-on: ubuntu-latest diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c927b3cc6..89c28aef4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,6 +11,7 @@ env: permissions: deployments: write + statuses: write jobs: create_deployment: @@ -289,3 +290,18 @@ jobs: description: 'Backend deployment failed' state: 'failure' deployment-id: ${{ needs.create_backend_deployment.outputs.deployment_id }} + + - name: Post commit status + uses: actions/github-script@v7 + with: + script: | + const state = '${{ needs.deploy_backend.result }}' === 'success' ? 'success' : 'failure'; + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ github.event.workflow_run.head_sha }}', + state: state, + target_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}', + description: state === 'success' ? 'Backend deployed to AWS' : 'Backend deployment failed', + context: 'deploy / backend' + }); diff --git a/.github/workflows/julia-tests.yml b/.github/workflows/julia-tests.yml new file mode 100644 index 000000000..63eead5e6 --- /dev/null +++ b/.github/workflows/julia-tests.yml @@ -0,0 +1,29 @@ +name: julia-tests + +on: + push: + branches: + - main + paths: + - 'packages/algjulia-interop/**' + - '.github/workflows/julia-tests.yml' + pull_request: + paths: + - 'packages/algjulia-interop/**' + - '.github/workflows/julia-tests.yml' + +jobs: + julia-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Julia + uses: julia-actions/setup-julia@v2 + with: + version: '1' + + - name: Tests + run: | + julia --project=packages/algjulia-interop -e 'import Pkg; Pkg.test()' diff --git a/.github/workflows/ui-components-tests.yml b/.github/workflows/ui-components-tests.yml new file mode 100644 index 000000000..73df839b7 --- /dev/null +++ b/.github/workflows/ui-components-tests.yml @@ -0,0 +1,53 @@ +name: ui-components-tests + +on: + push: + branches: + - main + paths: + - "packages/ui-components/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/ui-components-tests.yml" + pull_request: + paths: + - "packages/ui-components/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/ui-components-tests.yml" + +jobs: + ui-components-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('packages/ui-components/pnpm-lock.yaml') }} + + - name: Install dependencies + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + run: pnpm install + + - name: Install Playwright browser + run: pnpm --filter ./packages/ui-components exec playwright install --with-deps chromium-headless-shell + + - name: Run ui-components tests + run: pnpm --filter ./packages/ui-components run test diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e134c084..e49186561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,10 @@ announcement and a blog post. Minor versions are not announced but allow features and fixes to be released with greater frequency. Minor versions often include notable new features. -## [Unreleased] +## [v0.6.0](https://github.com/ToposInstitute/CatColab/releases/tag/v0.6.0) (2026-05-27) + +Blog post: [CatColab v0.6: +Starling](https://topos.institute/blog/2026-06-01-catcolab-0-6-starling/) ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e4b80355..647ab70dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,8 +69,4 @@ cargo clippy Try to remember to run these commands before making a PR. (If you forget, the CI will remind you.) -## Developer documentation -Additional documentation for developers: - -- [Fixing Hash Mismatches in Nix](./dev-docs/fixing-hash-mismatches.md) diff --git a/Cargo.lock b/Cargo.lock index 6913f607a..4836bed21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,7 +469,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.14", "serde", ] @@ -553,6 +553,7 @@ dependencies = [ "pretty", "rebop", "ref-cast", + "regex", "scopeguard", "sea-query", "serde", @@ -1010,6 +1011,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.101", + "unicode-xid", ] [[package]] @@ -3415,13 +3417,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.14", "regex-syntax 0.8.5", ] @@ -3436,9 +3438,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -5111,6 +5113,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_categories" version = "0.1.1" diff --git a/dev-docs/fixing-hash-mismatches.md b/dev-docs/fixing-hash-mismatches.md deleted file mode 100644 index 43e806c83..000000000 --- a/dev-docs/fixing-hash-mismatches.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Fixing hash mismatches in Nix" ---- - -### Fixing Hash Mismatches in Nix - -Nix uses **fixed-output derivations** to fetch external dependencies (from npm, crates.io, GitHub, etc.). -These derivations require a cryptographic hash to verify that the fetched content matches what's expected, -ensuring reproducibility and security. - -When a fixed-output dependency changes, so will its hash, causing a hash mismatch error in Nix. -This generally happens in two scenarios: -1. Intentional updates: You upgrade a package version -2. Upstream changes: The upstream package was modified without a version change - -If you encounter a hash mismatch without updating anything it should probably be investigated: it means -the external source changed unexpectedly. - -#### pnpm Dependencies - -This only applies to the `frontend` package. - -The following error occurs when a dependency has changed but the Nix hash has not: -``` -> ERR_PNPM_NO_OFFLINE_TARBALL  A package is missing from the store but cannot download it in offline mode. The missing package may be downloaded from https://registry.npmjs.org/@automerge/prosemirror/-/prosemirror-0.2.0-alpha.0.tgz. -> ERROR: pnpm failed to install dependencies -``` - -In this case you can follow the instructions given below the error message: -``` -> If you see ERR_PNPM_NO_OFFLINE_TARBALL above this, follow these to fix the issue: -> 1. Set pnpmDeps.hash to "" (empty string) -> 2. Build the derivation and wait for it to fail with a hash mismatch -> 3. Copy the 'got: sha256-' value back into the pnpmDeps.hash field -``` - -The hash is located in `packages/frontend/default.nix` within the `pkgs.fetchPnpmDeps` block. -You can search for the text "hash" to find it quickly. - -The frontend package can be built by running the command `nix build .#frontend` in the repository root. -This will build the minimum needed to print the hash mismatch described in the instructions. - - -#### Other Dependencies - -Dependencies other than `pnpm` will have hash mismatch errors that look like this: -``` -error: hash mismatch in fixed-output derivation '/nix/store/9ydq26vqirys8i3p9yx2ljxj8l9ynlgs-wasm-bindgen-cli-0.2.105.tar.gz.drv': - specified: sha256-M6WuGl7EruNopHZbqBpucu4RWz44/MSdv6f0zkYw+44= - got: sha256-zLPFFgnqAWq5R2KkaTGAYqVQswfBEYm9x3OPjx8DJRY= -``` - -These can be fixed by finding the `specified` hash in the Nix configs and replacing it with the `got` hash. -Currently all hashes except for `pnpm` are defined in `flake.nix` in the repo root. - -##### Dependencies with Hash Mismatches - -You may encounter hash mismatches for these dependencies: -- wasm-bindgen-cli -- rust-toolchain diff --git a/dev-docs/typedoc.json b/dev-docs/typedoc.json index bdbdc0cc5..848e735a3 100644 --- a/dev-docs/typedoc.json +++ b/dev-docs/typedoc.json @@ -2,6 +2,5 @@ "entryPoints": ["./index.ts"], "out": "output", "name": "CatColab: for developers", - "readme": "../CONTRIBUTING.md", - "projectDocuments": ["fixing-hash-mismatches.md"] + "readme": "../CONTRIBUTING.md" } diff --git a/flake.lock b/flake.lock index 900ca5784..12b4e35b8 100644 --- a/flake.lock +++ b/flake.lock @@ -115,6 +115,24 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -234,6 +252,27 @@ "type": "github" } }, + "pnpm2nix-nzbr": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1749022118, + "narHash": "sha256-7Qzmy1snKbxFBKoqUrfyxxmEB8rPxDdV7PQwRiAR01o=", + "owner": "FliegendeWurst", + "repo": "pnpm2nix-nzbr", + "rev": "35f88a41d29839b3989f31871263451c8e092cb1", + "type": "github" + }, + "original": { + "owner": "FliegendeWurst", + "repo": "pnpm2nix-nzbr", + "type": "github" + } + }, "root": { "inputs": { "agenix": "agenix", @@ -241,7 +280,8 @@ "deploy-rs": "deploy-rs", "fenix": "fenix", "nixos-generators": "nixos-generators", - "nixpkgs": "nixpkgs_4" + "nixpkgs": "nixpkgs_4", + "pnpm2nix-nzbr": "pnpm2nix-nzbr" } }, "rust-analyzer-src": { @@ -291,6 +331,21 @@ "type": "github" } }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "utils": { "inputs": { "systems": "systems_2" diff --git a/flake.nix b/flake.nix index 92eddc969..1619af6b4 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,11 @@ }; nixos-generators.url = "github:nix-community/nixos-generators"; + + pnpm2nix-nzbr = { + url = "github:FliegendeWurst/pnpm2nix-nzbr"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -70,19 +75,37 @@ pkgsLinux = nixpkgsFor linuxSystem; rustToolchainLinux = rustToolchainFor linuxSystem; - craneLib = (crane.mkLib pkgsLinux).overrideToolchain rustToolchainLinux; + # Per-system crane library + prebuilt cargo dependency layer. The + # frontend package is exposed for every system in `devShellSystems` so + # macOS developers can `nix build .#frontend` against the same wasm/api + # chain linux developers use; that requires per-system cargoArtifacts. + craneLibFor = + system: + let + pkgs = nixpkgsFor system; + in + (crane.mkLib pkgs).overrideToolchain (rustToolchainFor system); - cargoArtifacts = craneLib.buildDepsOnly { - src = craneLib.cleanCargoSource ./.; - strictDeps = true; - nativeBuildInputs = [ - pkgsLinux.pkg-config - ]; + cargoArtifactsFor = + system: + let + pkgs = nixpkgsFor system; + craneLib = craneLibFor system; + in + craneLib.buildDepsOnly { + src = craneLib.cleanCargoSource ./.; + strictDeps = true; + nativeBuildInputs = [ + pkgs.pkg-config + ]; - buildInputs = [ - pkgsLinux.openssl - ]; - }; + buildInputs = [ + pkgs.openssl + ]; + }; + + craneLib = craneLibFor linuxSystem; + cargoArtifacts = cargoArtifactsFor linuxSystem; # Generate devShells for each system devShellForSystem = @@ -194,90 +217,103 @@ # Example of how to build and test individual package built by nix: # nix build .#packages.x86_64-linux.automerge # node ./result/main.cjs - packages = { - x86_64-linux = { - catcolabApi = pkgsLinux.callPackage ./infrastructure/catcolab-api.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; - - backend = pkgsLinux.callPackage ./packages/backend/default.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; - - migrator = pkgsLinux.callPackage ./packages/migrator/default.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; - - catlog-wasm-browser = pkgsLinux.callPackage ./packages/catlog-wasm/default.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; - - document-types-wasm = pkgsLinux.callPackage ./packages/document-types/default.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; + # + # The frontend and its rust-derived dependencies (catcolabApi, + # catlog-wasm-browser, document-types-wasm) are exposed for every + # `devShellSystems` entry so macOS developers can `nix build .#frontend` + # natively. Linux-only outputs (backend, migrator, julia-fhs, rust-docs, + # catcolab-vm) stay under packages.x86_64-linux only. + packages = + let + frontendChainFor = + system: + let + pkgs = nixpkgsFor system; + craneArgs = { + craneLib = craneLibFor system; + cargoArtifacts = cargoArtifactsFor system; + inherit pkgs; + }; + frontendPackage = pkgs.callPackage ./packages/frontend/default.nix { + inherit inputs self; + rustToolchain = rustToolchainFor system; + pnpm2nix = inputs.pnpm2nix-nzbr; + }; + in + { + catcolabApi = pkgs.callPackage ./infrastructure/catcolab-api.nix craneArgs; + catlog-wasm-browser = pkgs.callPackage ./packages/catlog-wasm/default.nix craneArgs; + document-types-wasm = pkgs.callPackage ./packages/document-types/default.nix craneArgs; + frontend = frontendPackage.package; + frontend-tests = frontendPackage.tests; + }; - julia-fhs = (pkgsLinux.callPackage ./infrastructure/julia.nix { }).julia-fhs; + linuxOnlyPackages = { + backend = pkgsLinux.callPackage ./packages/backend/default.nix { + inherit craneLib cargoArtifacts; + pkgs = pkgsLinux; + }; - frontend = - (pkgsLinux.callPackage ./packages/frontend/default.nix { - inherit inputs rustToolchainLinux self; - }).package; + migrator = pkgsLinux.callPackage ./packages/migrator/default.nix { + inherit craneLib cargoArtifacts; + pkgs = pkgsLinux; + }; - frontend-tests = - (pkgsLinux.callPackage ./packages/frontend/default.nix { - inherit inputs rustToolchainLinux self; - }).tests; + julia-fhs = (pkgsLinux.callPackage ./infrastructure/julia.nix { }).julia-fhs; - rust-docs = pkgsLinux.callPackage ./infrastructure/rust-docs.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - }; + rust-docs = pkgsLinux.callPackage ./infrastructure/rust-docs.nix { + inherit craneLib cargoArtifacts; + pkgs = pkgsLinux; + }; - rust-docs-check = pkgsLinux.callPackage ./infrastructure/rust-docs.nix { - inherit craneLib cargoArtifacts; - pkgs = pkgsLinux; - checkMode = true; - }; + rust-docs-check = pkgsLinux.callPackage ./infrastructure/rust-docs.nix { + inherit craneLib cargoArtifacts; + pkgs = pkgsLinux; + checkMode = true; + }; - # VMs built with `nixos-rebuild build-vm` (like `nix build - # .#nixosConfigurations.catcolab-vm.config.system.build.vm`) are not the same - # as "traditional" VMs, which causes deploy-rs to fail when deploying to them. - # https://github.com/serokell/deploy-rs/issues/85#issuecomment-885782350 - # - # This is worked around by creating a full featured VM image. - # - # use: - # nix build .#catcolab-vm - # cp result/catcolab-vm.qcow2 catcolab-vm.qcow2 - # db-utils vm start - # deploy -s .#catcolab-vm - catcolab-vm = pkgsLinux.stdenv.mkDerivation { - name = "catcolab-vm"; - src = nixos-generators.nixosGenerate { - system = "x86_64-linux"; - format = "qcow"; - - modules = [ - ./infrastructure/hosts/catcolab-vm - ]; - - specialArgs = { - inherit inputs self; - rustToolchain = rustToolchainLinux; + # VMs built with `nixos-rebuild build-vm` (like `nix build + # .#nixosConfigurations.catcolab-vm.config.system.build.vm`) are not the same + # as "traditional" VMs, which causes deploy-rs to fail when deploying to them. + # https://github.com/serokell/deploy-rs/issues/85#issuecomment-885782350 + # + # This is worked around by creating a full featured VM image. + # + # use: + # nix build .#catcolab-vm + # cp result/catcolab-vm.qcow2 catcolab-vm.qcow2 + # db-utils vm start + # deploy -s .#catcolab-vm + catcolab-vm = pkgsLinux.stdenv.mkDerivation { + name = "catcolab-vm"; + src = nixos-generators.nixosGenerate { + system = "x86_64-linux"; + format = "qcow"; + + modules = [ + ./infrastructure/hosts/catcolab-vm + ]; + + specialArgs = { + inherit inputs self; + rustToolchain = rustToolchainLinux; + }; }; + installPhase = '' + mkdir -p $out + cp $src/nixos.qcow2 $out/catcolab-vm.qcow2 + ''; }; - installPhase = '' - mkdir -p $out - cp $src/nixos.qcow2 $out/catcolab-vm.qcow2 - ''; }; - }; - }; + in + builtins.listToAttrs ( + map (system: { + name = system; + value = + frontendChainFor system + // pkgsLinux.lib.optionalAttrs (system == linuxSystem) linuxOnlyPackages; + }) devShellSystems + ); # Create a NixOS configuration for each host nixosConfigurations = { diff --git a/oxlint.config.ts b/oxlint.config.ts index 0c2049db0..03009023d 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -75,6 +75,12 @@ export default defineConfig({ // Ternary-as-statement (`condition ? doA() : doB()`) is idiomatic in // this codebase for concise branching in callbacks. "no-unused-expressions": ["warn", { allowTernary: true }], + // Requires adding explicit `return undefined` in places seems to add too much noise + "consistent-return": "off", + // We use _ prefix in places + "no-underscore-dangle": "off", + // We have some tests that just see if anything breaks when things are run + "vitest/expect-expect": "off", // Allow underscore-prefixed names for intentionally unused bindings // (e.g., `_e` in catch blocks, `_unused` destructured fields). "no-unused-vars": [ diff --git a/package.json b/package.json index 6cf556014..f0853878b 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "devDependencies": { "depcheck": "^1.4.7", "oxfmt": "^0.37.0", - "oxlint": "^1.51.0", - "oxlint-tsgolint": "^0.16.0", - "stylelint": "^17.4.0", + "oxlint": "^1.62.0", + "oxlint-tsgolint": "^0.22.1", + "stylelint": "^17.9.1", "stylelint-config-standard": "^40.0.0", "tsx": "^4.16.5", "typedoc": "^0.26.5" diff --git a/packages/algjulia-interop/.gitignore b/packages/algjulia-interop/.gitignore index daa3c70fe..57e3341df 100644 --- a/packages/algjulia-interop/.gitignore +++ b/packages/algjulia-interop/.gitignore @@ -22,3 +22,7 @@ docs/site/ # Local preferences LocalPreferences.toml + +# Julia +test/Manifest.toml +test/Project.toml diff --git a/packages/algjulia-interop/Manifest.toml b/packages/algjulia-interop/Manifest.toml index ed8697aba..c1bbb7e0d 100644 --- a/packages/algjulia-interop/Manifest.toml +++ b/packages/algjulia-interop/Manifest.toml @@ -1,14 +1,14 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.12.5" +julia_version = "1.12.6" manifest_format = "2.0" -project_hash = "ae3caef2a0866469616d51a6bb9ac744f063ad55" +project_hash = "9451d3fd8333dba6c13ebb67dfb76723fc303e64" [[deps.ACSets]] deps = ["AlgebraicInterfaces", "Base64", "CompTime", "DataStructures", "JSON3", "MLStyle", "OrderedCollections", "PEG", "Permutations", "Pkg", "PrettyTables", "Random", "Reexport", "SHA", "StaticArrays", "StructEquality", "StructTypes", "Tables"] -git-tree-sha1 = "80ca69d6844aa5a1e67338b5362e393007c4c168" +git-tree-sha1 = "dabea268e85bfa2d87019772d1e5b5115542450f" uuid = "227ef7b5-1206-438b-ac65-934d6da304b8" -version = "0.2.26" +version = "0.2.28" [deps.ACSets.extensions] DataFramesACSetsExt = "DataFrames" @@ -48,14 +48,11 @@ path = "." uuid = "9ecda8fb-39ab-46a2-a496-7285fa6368c1" version = "0.1.1" - [deps.CatColabInterop.extensions] - CatlabExt = ["Catlab", "ACSets"] - [[deps.Catlab]] deps = ["ACSets", "AlgebraicInterfaces", "Colors", "CompTime", "Compose", "DataStructures", "GATlab", "GeneralizedGenerated", "JSON3", "LightXML", "LinearAlgebra", "Logging", "MLStyle", "PEG", "PrettyTables", "Random", "Reexport", "SparseArrays", "StaticArrays", "Statistics", "StructEquality", "StructTypes", "Tables"] -git-tree-sha1 = "bf515a1858d0dfd00887a3ba1daf587d14ec7b51" +git-tree-sha1 = "fadf5ec3e429cc88be1178b6baf687b99bb3177c" uuid = "134e5e36-593f-5add-ad60-77f754baafbe" -version = "0.17.4" +version = "0.17.5" [deps.Catlab.extensions] CatlabConvexExt = "Convex" @@ -217,9 +214,9 @@ version = "1.0.0" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.7.1" +version = "1.8.0" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] @@ -339,9 +336,9 @@ version = "1.1.10" [[deps.MbedTLS_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "926c6af3a037c68d02596a44c22ec3595f5f760b" +git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.6+0" +version = "2.28.1010+0" [[deps.Measures]] git-tree-sha1 = "b513cedd20d9c914783d8ad83d08120702bf2c77" @@ -418,9 +415,9 @@ version = "1.0.4" [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" +git-tree-sha1 = "5d5e0a78e971354b1c7bff0655d11fdc1b0e12c8" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.3" +version = "2.8.4" [[deps.Permutations]] deps = ["Combinatorics", "LinearAlgebra", "Random"] @@ -432,18 +429,16 @@ version = "0.4.23" deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.12.1" +weakdeps = ["REPL"] [deps.Pkg.extensions] REPLExt = "REPL" - [deps.Pkg.weakdeps] - REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +git-tree-sha1 = "edbeefc7a4889f528644251bdb5fc9ab5348bc2c" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.1" +version = "1.3.4" [[deps.Preferences]] deps = ["TOML"] @@ -457,16 +452,27 @@ uuid = "8162dcfd-2161-5ef2-ae6c-7681170c5f98" version = "0.2.0" [[deps.PrettyTables]] -deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "1101cd475833706e4d0e7b122218257178f48f34" +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "2.4.0" +version = "3.3.2" + + [deps.PrettyTables.extensions] + PrettyTablesTypstryExt = "Typstry" + + [deps.PrettyTables.weakdeps] + Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" +[[deps.REPL]] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + [[deps.Random]] deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/packages/algjulia-interop/Project.toml b/packages/algjulia-interop/Project.toml index aadcd1a4f..14fde7c13 100644 --- a/packages/algjulia-interop/Project.toml +++ b/packages/algjulia-interop/Project.toml @@ -8,20 +8,21 @@ authors = ["CatColab team"] ACSets = "227ef7b5-1206-438b-ac65-934d6da304b8" Catlab = "134e5e36-593f-5add-ad60-77f754baafbe" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" Oxygen = "df9a0d86-3283-4920-82dc-4555fc0d1d8b" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" - -[extensions] -CatlabExt = ["Catlab", "ACSets"] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] ACSets = "0.2.26" Catlab = "0.17.2" HTTP = "1.10.19" +JSON3 = "1.14.3" MLStyle = "0.4.17" Oxygen = "1.7.5" Reexport = "1.2.2" StructTypes = "1.11.0" +Test = "1.11.0" julia = "1.11" diff --git a/packages/algjulia-interop/README.md b/packages/algjulia-interop/README.md index dd37a4707..0517bb484 100644 --- a/packages/algjulia-interop/README.md +++ b/packages/algjulia-interop/README.md @@ -3,7 +3,7 @@ This small package makes functionality from [AlgebraicJulia](https://www.algebraicjulia.org/) available to CatColab. At this time, only a [Catlab.jl](https://github.com/AlgebraicJulia/Catlab.jl) service is -provided. Other packages (e.g. Decapodes.jl) will be added in the future. +provided. Other packages (e.g. [Decapodes.jl](https://github.com/AlgebraicJulia/Decapodes.jl))) will be added in the future. ## Usage diff --git a/packages/algjulia-interop/scripts/endpoint.jl b/packages/algjulia-interop/scripts/endpoint.jl index 5ab0413ca..4ebc18cb9 100644 --- a/packages/algjulia-interop/scripts/endpoint.jl +++ b/packages/algjulia-interop/scripts/endpoint.jl @@ -10,6 +10,9 @@ using CatColabInterop using Oxygen using HTTP +using Catlab +using ACSets + const port = parse(Int, get(ENV, "JULIA_PORT", "8080")) const CORS_HEADERS = [ @@ -31,14 +34,6 @@ function CorsHandler(handle) end end -defaults = [:Catlab,:ACSets] # all extensions to date - -# Dynamically load packages in command lin eargs -for pkg in (isempty(ARGS) ? defaults : Symbol.(ARGS) ) - @info "using $pkg" - @eval using $pkg -end - for m in methods(CatColabInterop.endpoint) sig = m.sig.parameters (length(sig)==2 && sig[2].instance isa Val) || error("Unexpected signature $sig") diff --git a/packages/algjulia-interop/src/CatColabInterop.jl b/packages/algjulia-interop/src/CatColabInterop.jl index 18bd4b81a..cc35146fd 100644 --- a/packages/algjulia-interop/src/CatColabInterop.jl +++ b/packages/algjulia-interop/src/CatColabInterop.jl @@ -4,12 +4,14 @@ export endpoint using Reexport -""" -Extend this method with endpoint(::Val{my_analysis_name}) in extension packages. -""" +""" Extend this method with endpoint(::Val{my_analysis_name}). """ function endpoint end include("Types.jl") @reexport using .Types +include("CatlabInterop.jl") +# Add more interops here... + + end # module diff --git a/packages/algjulia-interop/ext/CatlabExt.jl b/packages/algjulia-interop/src/CatlabInterop.jl similarity index 99% rename from packages/algjulia-interop/ext/CatlabExt.jl rename to packages/algjulia-interop/src/CatlabInterop.jl index 34fe01fde..792ecfa1f 100644 --- a/packages/algjulia-interop/ext/CatlabExt.jl +++ b/packages/algjulia-interop/src/CatlabInterop.jl @@ -1,4 +1,4 @@ -module CatlabExt +module CatlabInterop using ACSets using Catlab: Presentation, FreeSchema, Left diff --git a/packages/algjulia-interop/test/Project.toml b/packages/algjulia-interop/test/Project.toml deleted file mode 100644 index 84cb3f020..000000000 --- a/packages/algjulia-interop/test/Project.toml +++ /dev/null @@ -1,8 +0,0 @@ -[deps] -ACSets = "227ef7b5-1206-438b-ac65-934d6da304b8" -CatColabInterop = "9ecda8fb-39ab-46a2-a496-7285fa6368c1" -Catlab = "134e5e36-593f-5add-ad60-77f754baafbe" -HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" -Oxygen = "df9a0d86-3283-4920-82dc-4555fc0d1d8b" -StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" diff --git a/packages/algjulia-interop/test/TestCatlab.jl b/packages/algjulia-interop/test/TestCatlab.jl index eeaf86682..83a07bbd5 100644 --- a/packages/algjulia-interop/test/TestCatlab.jl +++ b/packages/algjulia-interop/test/TestCatlab.jl @@ -4,8 +4,7 @@ using CatColabInterop, Catlab using Catlab.CategoricalAlgebra.Pointwise.FunctorialDataMigrations.Yoneda: colimit_representables using HTTP, Test, Oxygen, JSON3 -const CatlabExt = Base.get_extension(CatColabInterop, :CatlabExt) - +import CatColabInterop.CatlabInterop # Example JSON #------------- body = read((@__DIR__)*"/data/diagrams/acset.json", String) @@ -16,11 +15,11 @@ p = JSON3.read(body, ModelDiagram) # Convert to ACSet #----------------- -schema, names = CatlabExt.model_to_schema(p.model) +schema, ids = CatColabInterop.CatlabInterop.model_to_schema(p.model) acset_type = AnonACSet( schema; type_assignment=Dict(a=>Nothing for a in schema.attrtypes)) y = yoneda(constructor(acset_type)) -data = CatlabExt.diagram_to_data(p.diagram, names) +data = CatlabInterop.diagram_to_data(p.diagram, ids) names, res = colimit_representables(data, y) S = acset_schema(res) @@ -36,8 +35,14 @@ expected[1, :g] = AttrVar(1) # Test final JSON output #----------------------- -expected_json = Dict(:Z => ["z"],:f => ["y"],:X => ["x"],:Y => ["y"],:g => ["z"]) -@test expected_json == CatlabExt.acset_to_json(res, schema, CatlabExt.make_names(res, names)) +expected_json = Dict( + "019a60e3-1785-72b9-90d2-84dc8bdddc85" => ["z"], + "019a6042-2872-7654-a9b4-67becc9ef693" => ["y"], + "019a6042-1241-77bd-8055-bfea5c206bc7" => ["x"], + "019a6042-1f1c-745e-bfea-753eeaccedf2" => ["y"], + "019a60e3-2ccf-74ef-a1e1-1c940564e1ca" => ["z"] +) +@test expected_json == CatlabInterop.acset_to_json(res, schema, ids, CatlabInterop.make_names(res, names)) # Optionally test the endpoint if running endpoint.jl: # resp = HTTP.post("http://127.0.0.1:8080/acsetcolim"; body) @@ -49,6 +54,6 @@ expected_json = Dict(:Z => ["z"],:f => ["y"],:X => ["x"],:Y => ["y"],:g => ["z"] @acset_type T(SchThree) exT = @acset T begin A=2; B=3; C=3; f=[2,3]; g=[2,3,2] end names = (z=(:C, 1), y= (:B, 1), x = (:A, 1), x2 = (:A, 2)) -@test CatlabExt.make_names(exT, names) == Dict(:A=>["x","x2"], :B=>["y","f(x)","f(x2)"], :C=>["z","g(y)","g(f(x))"]) +@test CatlabInterop.make_names(exT, names) == Dict(:A=>["x","x2"], :B=>["y","f(x)","f(x2)"], :C=>["z","g(y)","g(f(x))"]) end # module diff --git a/packages/backend/pkg/package.json b/packages/backend/pkg/package.json index 424094301..d20ccad18 100644 --- a/packages/backend/pkg/package.json +++ b/packages/backend/pkg/package.json @@ -9,6 +9,6 @@ }, "dependencies": { "@qubit-rs/client": "^0.4.5", - "typescript": "^5.6.2" + "typescript": "^6.0.3" } } diff --git a/packages/backend/pkg/pnpm-lock.yaml b/packages/backend/pkg/pnpm-lock.yaml index 814241c97..d276c483f 100644 --- a/packages/backend/pkg/pnpm-lock.yaml +++ b/packages/backend/pkg/pnpm-lock.yaml @@ -12,16 +12,16 @@ importers: specifier: ^0.4.5 version: 0.4.5 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^6.0.3 + version: 6.0.3 packages: '@qubit-rs/client@0.4.5': resolution: {integrity: sha512-L7pBAUs1sPWCkuoRq7rdwqXy1+iLOmOVhADxj+QoNY24yu4J1aiTUSlJJllK9rXRxfvkjRNEPSeUE5H+U3ncug==} - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -29,4 +29,4 @@ snapshots: '@qubit-rs/client@0.4.5': {} - typescript@5.6.2: {} + typescript@6.0.3: {} diff --git a/packages/catlog-wasm/src/model.rs b/packages/catlog-wasm/src/model.rs index 7dae0666c..18b021f73 100644 --- a/packages/catlog-wasm/src/model.rs +++ b/packages/catlog-wasm/src/model.rs @@ -747,7 +747,7 @@ pub fn elaborate_model( let theory = tt::theory::Theory::new(ustr("_").into(), theory_def); let ref_id = ustr(&ref_id); let mut elab = ElaboratorNext::new(theory.clone(), &instantiated.toplevel, ref_id); - let (ty_s, ty_v) = elab.notebook(notebook.0.formal_content()); + let (ty_s, ty_v) = elab.model_notebook(notebook.0.formal_content()); let (model, namespace) = tt::modelgen::Model::from_ty(&instantiated.toplevel, &theory.definition, &ty_v); Ok(DblModel { diff --git a/packages/catlog-wasm/src/model_diagram.rs b/packages/catlog-wasm/src/model_diagram.rs index d0b0b7b97..ea730a383 100644 --- a/packages/catlog-wasm/src/model_diagram.rs +++ b/packages/catlog-wasm/src/model_diagram.rs @@ -11,7 +11,8 @@ use wasm_bindgen::prelude::*; use catcolab_document_types::current::*; use catlog::dbl::model::{DblModel as _, DiscreteDblModel, FpDblModel, MutDblModel}; use catlog::dbl::model_diagram as diagram; -use catlog::dbl::model_morphism::DiscreteDblModelMapping; +use catlog::dbl::model_diagram::Diagram; +use catlog::dbl::model_morphism::{DiscreteDblModelMapping, MutDblModelMapping}; use catlog::one::FgCategory; use catlog::zero::{MutMapping, NameLookup, NameSegment, Namespace, QualifiedLabel, QualifiedName}; @@ -290,6 +291,7 @@ pub fn elaborate_diagram( match judgment { DiagramJudgment::Object(decl) => diagram.add_ob(&decl)?, DiagramJudgment::Morphism(decl) => diagram.add_mor(&decl)?, + DiagramJudgment::Instantiation(_) => todo!(), DiagramJudgment::Equation(_) => { return Err("Elaboration of equations in diagrams is not yet supported".into()); } diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index 2b95175c5..f01b6c7a1 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*; use catlog::dbl::theory::{self as theory, NonUnital, Unital}; use catlog::one::Path; use catlog::stdlib::{analyses, models, theories, theory_morphisms}; -use catlog::zero::{QualifiedLabel, name}; +use catlog::zero::name; use super::latex::LatexEquations; use super::model_morphism::{MotifOccurrence, MotifsOptions, motifs}; @@ -94,19 +94,13 @@ impl ThSchema { pub fn render_sql(&self, model: &DblModel, backend: &str) -> JsResult { analyses::sql::SQLBackend::try_from(backend) .and_then(|backend| { - analyses::sql::SQLAnalysis::new(backend).render( - model.discrete()?, - |id| { - model - .ob_generator_label(id) - .unwrap_or_else(|| QualifiedLabel::single("".into())) - }, - |id| { - model - .mor_generator_label(id) - .unwrap_or_else(|| QualifiedLabel::single("".into())) - }, - ) + analyses::sql::SQLAnalysis::new(backend) + .render( + model.discrete()?, + |id| model.ob_namespace.label_string(id), + |id| model.mor_namespace.label_string(id), + ) + .map_err(|e| format!("{}", e)) }) .into() } diff --git a/packages/catlog/Cargo.toml b/packages/catlog/Cargo.toml index dc0f9219c..66ea42bcb 100644 --- a/packages/catlog/Cargo.toml +++ b/packages/catlog/Cargo.toml @@ -16,7 +16,7 @@ stochastic = ["dep:rebop"] all-the-same = "1.1.0" bwd = "0.2.1" derivative = "2" -derive_more = { version = "2", features = ["constructor", "deref", "from", "into", "try_into"] } +derive_more = { version = "2", features = ["debug", "constructor", "deref", "from", "into", "try_into"] } duplicate = "2" egglog = { version = "2.0.0", default-features = false } ego-tree = "0.10" @@ -41,6 +41,7 @@ wasm-bindgen = { version = "0.2.100", optional = true } catcolab-document-types = { version = "0.1.0", path = "../document-types" } sea-query = { version = "0.32.7", optional = true } sqlformat = { version = "0.5.0", optional = true } +regex = "1.12.3" [dev-dependencies] clap = { version = "4.5.47", features = ["derive"] } diff --git a/packages/catlog/examples/tt/notebook/heat_eq_analysis.json b/packages/catlog/examples/tt/notebook/heat_eq_analysis.json new file mode 100644 index 000000000..7051ffda5 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/heat_eq_analysis.json @@ -0,0 +1,560 @@ +{ + "model": { + "obGenerators": [ + { + "id": "019df3f4-8443-71d6-a106-e211b1f1a359", + "label": [ + "0-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + }, + { + "id": "019df509-56a6-7656-98e6-896813fa1052", + "label": [ + "1-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + }, + { + "id": "019df509-6110-70d2-91fd-d81a3b4eab88", + "label": [ + "2-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + }, + { + "id": "019df3f4-3ec3-7069-94bd-5f5097133079", + "label": [ + "Dual 0-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + }, + { + "id": "019df509-6978-72bf-9362-0e085ed78843", + "label": [ + "Dual 1-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + }, + { + "id": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "label": [ + "Dual 2-Form" + ], + "obType": { + "tag": "Basic", + "content": "Object" + } + } + ], + "morGenerators": [ + { + "id": "019df920-3a9a-70c4-821e-a871751e58ec", + "label": [ + "multiplication" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + }, + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df91e-8033-716b-8526-12e08e9bec59", + "label": [ + "laplace" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df50c-6981-7248-8a82-b1da5d63ab63", + "label": [ + "inv-laplace" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df509-e051-77aa-9899-db60385d3b57", + "label": [ + "0-d" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-56a6-7656-98e6-896813fa1052" + } + }, + { + "id": "019df50a-6e39-7216-9b89-87168f762a5c", + "label": [ + "1-star" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df509-56a6-7656-98e6-896813fa1052" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-6978-72bf-9362-0e085ed78843" + } + }, + { + "id": "019df50b-da40-7399-9c85-094a02bde699", + "label": [ + "sharp-flat" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df509-56a6-7656-98e6-896813fa1052" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-56a6-7656-98e6-896813fa1052" + } + }, + { + "id": "019df50d-0c28-71f9-bcc4-643ca621044d", + "label": [ + "dual 0-d" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-3ec3-7069-94bd-5f5097133079" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-6978-72bf-9362-0e085ed78843" + } + }, + { + "id": "019df508-fe6a-751c-92e0-ed1ffccbfb20", + "label": [ + "0-inv-star" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-3ec3-7069-94bd-5f5097133079" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-6110-70d2-91fd-d81a3b4eab88" + } + }, + { + "id": "019df526-a755-75f8-8528-f381f4feec4d", + "label": [ + "dual 1-d" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df509-6978-72bf-9362-0e085ed78843" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + }, + { + "id": "019df526-5735-724b-b232-140c13a4a438", + "label": [ + "2-inv-star" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df509-9301-77d7-bdd5-3ffaea8a24a1", + "label": [ + "partial" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + }, + { + "id": "019df522-b423-7419-880c-7c8b6f62eb38", + "label": [ + "dp-wedge-10" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df509-6978-72bf-9362-0e085ed78843" + }, + { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df509-56a6-7656-98e6-896813fa1052" + } + }, + { + "id": "019df5ea-62d9-761e-8c9c-9b5cf73e7240", + "label": [ + "-" + ], + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0" + } + } + ] + }, + "diagram": { + "obGenerators": [ + { + "id": "019df91a-38ee-7001-89aa-0cdfb89f2b69", + "label": [ + "u" + ], + "obType": { + "tag": "Basic", + "content": "Object" + }, + "over": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df91a-6c2d-75d1-8089-9485a756151c", + "label": [ + "partial-u" + ], + "obType": { + "tag": "Basic", + "content": "Object" + }, + "over": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + }, + { + "id": "019df91b-7974-738f-ab90-1474c2aa6914", + "label": [ + "k" + ], + "obType": { + "tag": "Basic", + "content": "Object" + }, + "over": { + "tag": "Basic", + "content": "019df3f4-8443-71d6-a106-e211b1f1a359" + } + } + ], + "morGenerators": [ + { + "id": "019df91e-0f0e-778e-838c-0de048eb9aa3", + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "over": { + "tag": "Basic", + "content": "019df91e-8033-716b-8526-12e08e9bec59" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df91a-38ee-7001-89aa-0cdfb89f2b69" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df91e-5e6e-754a-99ef-47d3b5465a39" + } + }, + { + "id": "019df91e-cfe2-728f-81ef-fbf11b684885", + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "over": { + "tag": "Basic", + "content": "019df509-9301-77d7-bdd5-3ffaea8a24a1" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df91a-38ee-7001-89aa-0cdfb89f2b69" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df91a-6c2d-75d1-8089-9485a756151c" + } + }, + { + "id": "019df91b-54c0-75ee-88d8-3a946f1d4ee2", + "morType": { + "tag": "Basic", + "content": "Multihom" + }, + "over": { + "tag": "Basic", + "content": "019df920-3a9a-70c4-821e-a871751e58ec" + }, + "dom": { + "tag": "List", + "content": { + "modality": "List", + "objects": [ + { + "tag": "Basic", + "content": "019df91e-5e6e-754a-99ef-47d3b5465a39" + }, + { + "tag": "Basic", + "content": "019df91b-7974-738f-ab90-1474c2aa6914" + } + ] + } + }, + "cod": { + "tag": "Basic", + "content": "019df91a-6c2d-75d1-8089-9485a756151c" + } + } + ] + }, + "analysis": { + "duration": 50, + "plotVariables": [ + "019df91a-6c2d-75d1-8089-9485a756151c" + ], + "domain": "Plane", + "mesh": "Rectangle", + "initialConditions": { + "019df91a-38ee-7001-89aa-0cdfb89f2b69": "Gaussian", + "019df91a-6c2d-75d1-8089-9485a756151c": "Gaussian", + "019df91b-7974-738f-ab90-1474c2aa6914": "Constant" + } + } +} diff --git a/packages/catlog/examples/tt/notebook/heat_eq_petri.json b/packages/catlog/examples/tt/notebook/heat_eq_petri.json new file mode 100644 index 000000000..2d5f11a39 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/heat_eq_petri.json @@ -0,0 +1,228 @@ +{ + "diagramIn": { + "_id": "019ea9ae-f867-7373-a70b-5a860e6f8c06", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "diagram-in" + }, + "name": "Heat Equation", + "notebook": { + "cellContents": { + "019ea9af-e082-7468-a8b2-83cb49b28357": { + "content": { + "id": "019ea9af-e081-750a-9908-daf11a552da4", + "name": "u", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019ea9af-e082-7468-a8b2-83cb49b28357", + "tag": "formal" + }, + "019ea9af-f8ab-72b4-aad2-d81325a4e3e0": { + "content": { + "id": "019ea9af-f8ab-72b4-aad2-d623ce6613fe", + "name": "partial-u", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019ea9af-f8ab-72b4-aad2-d81325a4e3e0", + "tag": "formal" + }, + "019ea9b0-0e80-708d-9a8c-3e4f53c7b00b": { + "content": { + "id": "019ea9b0-0e80-708d-9a8c-3979c09e762d", + "name": "k", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019ea9b0-0e80-708d-9a8c-3e4f53c7b00b", + "tag": "formal" + }, + "019ea9b0-23e4-7758-9b54-013344564998": { + "content": { + "id": "019ea9b0-23e4-7758-9b53-fed672c047e5", + "name": "anon-0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019ea9b0-23e4-7758-9b54-013344564998", + "tag": "formal" + }, + "019ea9b0-3404-7440-8ecb-ea5c43b9af99": { + "content": { + "cod": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-f8ab-72b4-aad2-d623ce6613fe", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-e081-750a-9908-daf11a552da4", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019ea9b0-3404-7440-8ecb-e686c716a09c", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "", + "over": { + "content": "019ea9af-2fe2-7139-b2df-881d5af7703b", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019ea9b0-3404-7440-8ecb-ea5c43b9af99", + "tag": "formal" + }, + "019ea9b0-59a4-72ac-a395-99003d1c3157": { + "content": { + "cod": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9b0-23e4-7758-9b53-fed672c047e5", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-e081-750a-9908-daf11a552da4", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019ea9b0-59a3-738c-b9ec-3ef91df1f1d1", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "", + "over": { + "content": "019ea9b0-7e0d-752f-b77d-a7ec0956ea8c", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019ea9b0-59a4-72ac-a395-99003d1c3157", + "tag": "formal" + }, + "019ea9b0-a2db-707a-8acb-a18a182dbac0": { + "content": { + "cod": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-f8ab-72b4-aad2-d623ce6613fe", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9b0-23e4-7758-9b53-fed672c047e5", + "tag": "Basic" + }, + { + "content": "019ea9b0-0e80-708d-9a8c-3979c09e762d", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019ea9b0-a2db-707a-8acb-9edcb61d66b3", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "", + "over": { + "content": "019ea9af-5ddd-70c2-a9a4-31ea4065da9e", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019ea9b0-a2db-707a-8acb-a18a182dbac0", + "tag": "formal" + } + }, + "cellOrder": [ + "019ea9af-e082-7468-a8b2-83cb49b28357", + "019ea9af-f8ab-72b4-aad2-d81325a4e3e0", + "019ea9b0-0e80-708d-9a8c-3e4f53c7b00b", + "019ea9b0-23e4-7758-9b54-013344564998", + "019ea9b0-3404-7440-8ecb-ea5c43b9af99", + "019ea9b0-59a4-72ac-a395-99003d1c3157", + "019ea9b0-a2db-707a-8acb-a18a182dbac0" + ] + }, + "type": "diagram", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/heat_eq_petri_model.json b/packages/catlog/examples/tt/notebook/heat_eq_petri_model.json new file mode 100644 index 000000000..4da108bdc --- /dev/null +++ b/packages/catlog/examples/tt/notebook/heat_eq_petri_model.json @@ -0,0 +1,270 @@ +{ + "name": "Testing Petri-Net", + "notebook": { + "cellContents": { + "019ea9af-269b-721a-9cf1-d2de10f159d0": { + "content": { + "id": "019ea9af-269b-721a-9cf1-ce539f888301", + "name": "Form0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019ea9af-269b-721a-9cf1-d2de10f159d0", + "tag": "formal" + }, + "019ea9af-2fe2-7139-b2df-8d84761e149b": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019ea9af-2fe2-7139-b2df-881d5af7703b", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "partial", + "tag": "morphism" + }, + "id": "019ea9af-2fe2-7139-b2df-8d84761e149b", + "tag": "formal" + }, + "019ea9af-5ddd-70c2-a9a4-364cc20b5879": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019ea9af-5ddd-70c2-a9a4-31ea4065da9e", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "multiplication", + "tag": "morphism" + }, + "id": "019ea9af-5ddd-70c2-a9a4-364cc20b5879", + "tag": "formal" + }, + "019ea9af-88a5-76da-b6db-4af42a0cf5c8": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + }, + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019ea9af-88a5-76da-b6db-457deb58b8a6", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "wedge", + "tag": "morphism" + }, + "id": "019ea9af-88a5-76da-b6db-4af42a0cf5c8", + "tag": "formal" + }, + "019ea9b0-7e0d-752f-b77d-ab2d32f9a41d": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019ea9af-269b-721a-9cf1-ce539f888301", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019ea9b0-7e0d-752f-b77d-a7ec0956ea8c", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "lapl", + "tag": "morphism" + }, + "id": "019ea9b0-7e0d-752f-b77d-ab2d32f9a41d", + "tag": "formal" + } + }, + "cellOrder": [ + "019ea9af-269b-721a-9cf1-d2de10f159d0", + "019ea9af-2fe2-7139-b2df-8d84761e149b", + "019ea9b0-7e0d-752f-b77d-ab2d32f9a41d", + "019ea9af-5ddd-70c2-a9a4-364cc20b5879", + "019ea9af-88a5-76da-b6db-4af42a0cf5c8" + ] + }, + "theory": "petri-net", + "type": "model", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/Klausmeier.json b/packages/catlog/examples/tt/notebook/klausmeier/Klausmeier.json new file mode 100644 index 000000000..6263b4b46 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/Klausmeier.json @@ -0,0 +1,117 @@ +{ + "diagramIn": { + "_id": "019df1c9-1c72-72d3-a0f7-9de7af7db5d5", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "diagram-in" + }, + "name": "Klausmeier", + "notebook": { + "cellContents": { + "019eb903-3b40-7469-8e77-7f0d150775e5": { + "content": { + "diagram": { + "_id": "019eb37e-eb26-7283-8c68-63d4cb8cd1f7", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "instantiation" + }, + "id": "019eb903-3b40-7469-8e77-7b7019bd82ac", + "name": "Hydrodynamics", + "specializations": [ + { + "id": "019eb746-8257-751b-bd10-1963c8921101", + "ob": { + "content": "019eb916-dad1-71bc-bc0a-e1f22763b31f", + "tag": "Basic" + } + }, + { + "id": "019eb746-8606-7423-a8e5-b83e191cca53", + "ob": { + "content": "019eb916-f032-75e9-b7b5-8a3a08324a02", + "tag": "Basic" + } + } + ], + "tag": "instantiation" + }, + "id": "019eb903-3b40-7469-8e77-7f0d150775e5", + "tag": "formal" + }, + "019eb916-9299-7489-836f-84a1eb4bc9eb": { + "content": { + "diagram": { + "_id": "019eb288-c310-7f33-b2c3-171279589942", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "instantiation" + }, + "id": "019eb916-9299-7489-836f-80bd1830f913", + "name": "Phytodynamics", + "specializations": [ + { + "id": "019eb28c-ceec-779a-b09d-67307cb2d5c2", + "ob": { + "content": "019eb916-dad1-71bc-bc0a-e1f22763b31f", + "tag": "Basic" + } + }, + { + "id": "019eb289-de33-7179-8cb8-4d3ec8facbea", + "ob": { + "content": "019eb916-f032-75e9-b7b5-8a3a08324a02", + "tag": "Basic" + } + } + ], + "tag": "instantiation" + }, + "id": "019eb916-9299-7489-836f-84a1eb4bc9eb", + "tag": "formal" + }, + "019eb916-dad1-71bc-bc0a-e68649ea9d60": { + "content": { + "id": "019eb916-dad1-71bc-bc0a-e1f22763b31f", + "name": "n", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb916-dad1-71bc-bc0a-e68649ea9d60", + "tag": "formal" + }, + "019eb916-f032-75e9-b7b5-8fe879ae1ca6": { + "content": { + "id": "019eb916-f032-75e9-b7b5-8a3a08324a02", + "name": "w", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb916-f032-75e9-b7b5-8fe879ae1ca6", + "tag": "formal" + } + }, + "cellOrder": [ + "019eb903-3b40-7469-8e77-7f0d150775e5", + "019eb916-9299-7489-836f-84a1eb4bc9eb", + "019eb916-dad1-71bc-bc0a-e68649ea9d60", + "019eb916-f032-75e9-b7b5-8fe879ae1ca6" + ] + }, + "type": "diagram", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json b/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json new file mode 100644 index 000000000..1bf784861 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json @@ -0,0 +1,529 @@ +{ + "diagramIn": { + "_id": "019df1c9-1c72-72d3-a0f7-9de7af7db5d5", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "diagram-in" + }, + "name": "Hydrodynamics", + "notebook": { + "cellContents": { + "019eb746-7967-751a-b4d2-f2882f92b53e": { + "content": { + "id": "019eb746-7967-751a-b4d2-ed28cb3760cf", + "name": "a", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-7967-751a-b4d2-f2882f92b53e", + "tag": "formal" + }, + "019eb746-7dde-735d-aacc-2ba8123f2ba0": { + "content": { + "id": "019eb746-7dde-735d-aacc-25e644c576e3", + "name": "k", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-7dde-735d-aacc-2ba8123f2ba0", + "tag": "formal" + }, + "019eb746-8257-751b-bd10-1dd3896594d4": { + "content": { + "id": "019eb746-8257-751b-bd10-1963c8921101", + "name": "n", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-8257-751b-bd10-1dd3896594d4", + "tag": "formal" + }, + "019eb746-8606-7423-a8e5-bcf90d58b5c5": { + "content": { + "id": "019eb746-8606-7423-a8e5-b83e191cca53", + "name": "w", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-8606-7423-a8e5-bcf90d58b5c5", + "tag": "formal" + }, + "019eb746-988f-74c8-905a-c752ab2e45fa": { + "content": { + "id": "019eb746-988f-74c8-905a-c2ca890efc41", + "name": "dX", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-988f-74c8-905a-c752ab2e45fa", + "tag": "formal" + }, + "019eb746-a49e-70d7-8cc9-df553217d6c9": { + "content": { + "id": "019eb746-a49e-70d7-8cc9-dbfe12358d3a", + "name": "x0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-a49e-70d7-8cc9-df553217d6c9", + "tag": "formal" + }, + "019eb746-a7c8-7378-9a67-c7edff22b468": { + "content": { + "id": "019eb746-a7c7-77ef-908b-7158346b3d86", + "name": "x1", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-a7c8-7378-9a67-c7edff22b468", + "tag": "formal" + }, + "019eb746-acf7-768e-8ce2-6af09f67a4a7": { + "content": { + "id": "019eb746-acf7-768e-8ce2-66673d0af4d4", + "name": "x2", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-acf7-768e-8ce2-6af09f67a4a7", + "tag": "formal" + }, + "019eb746-b086-714a-a950-765f82a23d06": { + "content": { + "id": "019eb746-b086-714a-a950-73f7f029f09f", + "name": "x3", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-b086-714a-a950-765f82a23d06", + "tag": "formal" + }, + "019eb746-b447-7170-a93b-cdfdf44f4ae8": { + "content": { + "id": "019eb746-b447-7170-a93b-c871e5023af5", + "name": "x4", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-b447-7170-a93b-cdfdf44f4ae8", + "tag": "formal" + }, + "019eb746-b76e-7434-9dc0-2dcb1bae72be": { + "content": { + "id": "019eb746-b76e-7434-9dc0-2b6d9f1966bd", + "name": "x5", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb746-b76e-7434-9dc0-2dcb1bae72be", + "tag": "formal" + }, + "019eb747-40b7-70a3-8abe-28e1966327d4": { + "content": { + "id": "019eb747-40b7-70a3-8abe-266f406f14c2", + "name": "x6", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb747-40b7-70a3-8abe-28e1966327d4", + "tag": "formal" + }, + "019eb747-7946-721a-93fb-5e97b3f6b759": { + "content": { + "cod": { + "content": "019eb746-a49e-70d7-8cc9-dbfe12358d3a", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-7967-751a-b4d2-ed28cb3760cf", + "tag": "Basic" + }, + { + "content": "019eb746-8606-7423-a8e5-b83e191cca53", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb747-7946-721a-93fb-59e8ed9037c3", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb748-0792-7608-a9af-e803e3886a10", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb747-7946-721a-93fb-5e97b3f6b759", + "tag": "formal" + }, + "019eb747-8047-7375-b530-c3756b231031": { + "content": { + "cod": { + "content": "019eb746-a7c7-77ef-908b-7158346b3d86", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-8257-751b-bd10-1963c8921101", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb747-8047-7375-b530-be1846739c83", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28e-b02f-754a-b7ec-7c9df716cac4", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb747-8047-7375-b530-c3756b231031", + "tag": "formal" + }, + "019eb747-84d7-762d-aa5b-9fb72e2f6e2d": { + "content": { + "cod": { + "content": "019eb746-acf7-768e-8ce2-66673d0af4d4", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-8606-7423-a8e5-b83e191cca53", + "tag": "Basic" + }, + { + "content": "019eb746-a7c7-77ef-908b-7158346b3d86", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb747-84d7-762d-aa5b-9b260d1203ae", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28f-792e-7414-807f-3f3ba6138ed7", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb747-84d7-762d-aa5b-9fb72e2f6e2d", + "tag": "formal" + }, + "019eb747-8fae-734e-8183-4869e2a473bc": { + "content": { + "cod": { + "content": "019eb746-b086-714a-a950-73f7f029f09f", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-a49e-70d7-8cc9-dbfe12358d3a", + "tag": "Basic" + }, + { + "content": "019eb746-acf7-768e-8ce2-66673d0af4d4", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb747-8fae-734e-8183-447793c4c261", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb290-9d67-769e-a374-775d25df2eed", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb747-8fae-734e-8183-4869e2a473bc", + "tag": "formal" + }, + "019eb747-937a-70bd-9fef-0f1f068561f3": { + "content": { + "cod": { + "content": "019eb746-b447-7170-a93b-c871e5023af5", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-988f-74c8-905a-c2ca890efc41", + "tag": "Basic" + }, + { + "content": "019eb746-8606-7423-a8e5-b83e191cca53", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb747-9379-74b1-aa0e-4102c651df86", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb74c-be64-7701-826b-54c6b28f28d9", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb747-937a-70bd-9fef-0f1f068561f3", + "tag": "formal" + }, + "019eb74d-42df-74af-bde7-5ada8dd94a9e": { + "content": { + "cod": { + "content": "019eb746-b76e-7434-9dc0-2b6d9f1966bd", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-7dde-735d-aacc-25e644c576e3", + "tag": "Basic" + }, + { + "content": "019eb746-b447-7170-a93b-c871e5023af5", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb74d-42df-74af-bde7-57b9fcef0c4d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28f-792e-7414-807f-3f3ba6138ed7", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb74d-42df-74af-bde7-5ada8dd94a9e", + "tag": "formal" + }, + "019eb74d-93ff-7560-ba46-0beedccf25e2": { + "content": { + "cod": { + "content": "019eb747-40b7-70a3-8abe-266f406f14c2", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-b086-714a-a950-73f7f029f09f", + "tag": "Basic" + }, + { + "content": "019eb746-b76e-7434-9dc0-2b6d9f1966bd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb74d-93fe-7573-b290-0b467ca0b667", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb293-c69e-75eb-b499-a8a62a67e559", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb74d-93ff-7560-ba46-0beedccf25e2", + "tag": "formal" + }, + "019eb74d-d4e0-77ec-99f3-b36d4cc8a6e6": { + "content": { + "cod": { + "content": "019eb747-40b7-70a3-8abe-266f406f14c2", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb746-8606-7423-a8e5-b83e191cca53", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb74d-d4e0-77ec-99f3-ac9f9137c891", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb294-5186-76ff-9cc6-49124d01a884", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb74d-d4e0-77ec-99f3-b36d4cc8a6e6", + "tag": "formal" + } + }, + "cellOrder": [ + "019eb746-7967-751a-b4d2-f2882f92b53e", + "019eb746-7dde-735d-aacc-2ba8123f2ba0", + "019eb746-8257-751b-bd10-1dd3896594d4", + "019eb746-8606-7423-a8e5-bcf90d58b5c5", + "019eb746-988f-74c8-905a-c752ab2e45fa", + "019eb746-a49e-70d7-8cc9-df553217d6c9", + "019eb746-a7c8-7378-9a67-c7edff22b468", + "019eb746-acf7-768e-8ce2-6af09f67a4a7", + "019eb746-b086-714a-a950-765f82a23d06", + "019eb746-b447-7170-a93b-cdfdf44f4ae8", + "019eb746-b76e-7434-9dc0-2dcb1bae72be", + "019eb747-40b7-70a3-8abe-28e1966327d4", + "019eb747-7946-721a-93fb-5e97b3f6b759", + "019eb747-8047-7375-b530-c3756b231031", + "019eb747-84d7-762d-aa5b-9fb72e2f6e2d", + "019eb747-8fae-734e-8183-4869e2a473bc", + "019eb747-937a-70bd-9fef-0f1f068561f3", + "019eb74d-42df-74af-bde7-5ada8dd94a9e", + "019eb74d-93ff-7560-ba46-0beedccf25e2", + "019eb74d-d4e0-77ec-99f3-b36d4cc8a6e6" + ] + }, + "type": "diagram", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/model_dec_fragment.json b/packages/catlog/examples/tt/notebook/klausmeier/model_dec_fragment.json new file mode 100644 index 000000000..35096213d --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/model_dec_fragment.json @@ -0,0 +1,824 @@ +{ + "name": "DEC Fragment", + "notebook": { + "cellContents": { + "019df3f4-3ec3-7069-94bd-60cf0befac08": { + "content": { + "id": "019df3f4-3ec3-7069-94bd-5f5097133079", + "name": "Dual 0-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-3ec3-7069-94bd-60cf0befac08", + "tag": "formal" + }, + "019df3f4-7663-7519-ba02-a2826c5d8708": { + "content": { + "id": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "name": "Dual 2-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-7663-7519-ba02-a2826c5d8708", + "tag": "formal" + }, + "019df3f4-8444-70e8-b8a3-6eff98e1c9d4": { + "content": { + "id": "019df3f4-8443-71d6-a106-e211b1f1a359", + "name": "0-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-8444-70e8-b8a3-6eff98e1c9d4", + "tag": "formal" + }, + "019df508-fe6a-751c-92e0-f04f0cf69a1a": { + "content": { + "cod": { + "content": "019df509-6110-70d2-91fd-d81a3b4eab88", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df508-fe6a-751c-92e0-ed1ffccbfb20", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "0-inv-star", + "tag": "morphism" + }, + "id": "019df508-fe6a-751c-92e0-f04f0cf69a1a", + "tag": "formal" + }, + "019df509-56a6-7656-98e6-8f4da9c812c9": { + "content": { + "id": "019df509-56a6-7656-98e6-896813fa1052", + "name": "1-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-56a6-7656-98e6-8f4da9c812c9", + "tag": "formal" + }, + "019df509-6110-70d2-91fd-dd2a8dade102": { + "content": { + "id": "019df509-6110-70d2-91fd-d81a3b4eab88", + "name": "2-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-6110-70d2-91fd-dd2a8dade102", + "tag": "formal" + }, + "019df509-6978-72bf-9362-13b7ad373887": { + "content": { + "id": "019df509-6978-72bf-9362-0e085ed78843", + "name": "Dual 1-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-6978-72bf-9362-13b7ad373887", + "tag": "formal" + }, + "019df509-9301-77d7-bdd5-43a3d908fbb8": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df509-9301-77d7-bdd5-3ffaea8a24a1", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial", + "tag": "morphism" + }, + "id": "019df509-9301-77d7-bdd5-43a3d908fbb8", + "tag": "formal" + }, + "019df509-e051-77aa-9899-df703a8c58cf": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df509-e051-77aa-9899-db60385d3b57", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "0-d", + "tag": "morphism" + }, + "id": "019df509-e051-77aa-9899-df703a8c58cf", + "tag": "formal" + }, + "019df50a-6e39-7216-9b89-8a070cefc041": { + "content": { + "cod": { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50a-6e39-7216-9b89-87168f762a5c", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "1-star", + "tag": "morphism" + }, + "id": "019df50a-6e39-7216-9b89-8a070cefc041", + "tag": "formal" + }, + "019df50b-da40-7399-9c85-0f8ae1241f0d": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50b-da40-7399-9c85-094a02bde699", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "sharp-flat", + "tag": "morphism" + }, + "id": "019df50b-da40-7399-9c85-0f8ae1241f0d", + "tag": "formal" + }, + "019df50c-6981-7248-8a82-b42acdb471f0": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50c-6981-7248-8a82-b1da5d63ab63", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "inv-laplace", + "tag": "morphism" + }, + "id": "019df50c-6981-7248-8a82-b42acdb471f0", + "tag": "formal" + }, + "019df50d-0c28-71f9-bcc4-69bbe9ca152e": { + "content": { + "cod": { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50d-0c28-71f9-bcc4-643ca621044d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dual 0-d", + "tag": "morphism" + }, + "id": "019df50d-0c28-71f9-bcc4-69bbe9ca152e", + "tag": "formal" + }, + "019df522-b423-7419-880c-82c4601bf9c4": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df522-b423-7419-880c-7c8b6f62eb38", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dp-wedge-10", + "tag": "morphism" + }, + "id": "019df522-b423-7419-880c-82c4601bf9c4", + "tag": "formal" + }, + "019df526-5735-724b-b232-1a1aafd58233": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df526-5735-724b-b232-140c13a4a438", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "2-inv-star", + "tag": "morphism" + }, + "id": "019df526-5735-724b-b232-1a1aafd58233", + "tag": "formal" + }, + "019df526-a755-75f8-8528-f74ced38a971": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df526-a755-75f8-8528-f381f4feec4d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dual 1-d", + "tag": "morphism" + }, + "id": "019df526-a755-75f8-8528-f74ced38a971", + "tag": "formal" + }, + "019df528-97dd-75f8-b9fd-c193b73d0b21": { + "content": "I assume this is correct...", + "id": "019df528-97dd-75f8-b9fd-c193b73d0b21", + "tag": "rich-text" + }, + "019df5ea-62d9-761e-8c9c-9fc2629f83f0": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5ea-62d9-761e-8c9c-9b5cf73e7240", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "-", + "tag": "morphism" + }, + "id": "019df5ea-62d9-761e-8c9c-9fc2629f83f0", + "tag": "formal" + }, + "019df91e-8033-716b-8526-1414019c5649": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df91e-8033-716b-8526-12e08e9bec59", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "laplace", + "tag": "morphism" + }, + "id": "019df91e-8033-716b-8526-1414019c5649", + "tag": "formal" + }, + "019df920-3a9a-70c4-821e-ac6de6a10c70": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df920-3a9a-70c4-821e-a871751e58ec", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "multiplication", + "tag": "morphism" + }, + "id": "019df920-3a9a-70c4-821e-ac6de6a10c70", + "tag": "formal" + }, + "019eb28d-f5e6-777f-aae0-cf98e6ba85d2": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28d-f5e6-777f-aae0-cb7d08bd35a1", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "multiplication-dual0", + "tag": "morphism" + }, + "id": "019eb28d-f5e6-777f-aae0-cf98e6ba85d2", + "tag": "formal" + }, + "019eb28e-b02f-754a-b7ec-802d65be072e": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28e-b02f-754a-b7ec-7c9df716cac4", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "square-dual0", + "tag": "morphism" + }, + "id": "019eb28e-b02f-754a-b7ec-802d65be072e", + "tag": "formal" + }, + "019eb28f-792e-7414-807f-4151fa624da8": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28f-792e-7414-807f-3f3ba6138ed7", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "multiplication-0dual0", + "tag": "morphism" + }, + "id": "019eb28f-792e-7414-807f-4151fa624da8", + "tag": "formal" + }, + "019eb290-1d17-720f-855d-5310d112eea2": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb290-1d17-720f-855d-4d03eb5d79d1", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "laplace-dual0", + "tag": "morphism" + }, + "id": "019eb290-1d17-720f-855d-5310d112eea2", + "tag": "formal" + }, + "019eb290-9d68-719b-8c67-c684659a091e": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb290-9d67-769e-a374-775d25df2eed", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "subtract-dual0", + "tag": "morphism" + }, + "id": "019eb290-9d68-719b-8c67-c684659a091e", + "tag": "formal" + }, + "019eb293-c69f-7049-ae1f-1f6bacfca350": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb293-c69e-75eb-b499-a8a62a67e559", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "add-dual0", + "tag": "morphism" + }, + "id": "019eb293-c69f-7049-ae1f-1f6bacfca350", + "tag": "formal" + }, + "019eb294-5186-76ff-9cc6-4f95974a1e1c": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb294-5186-76ff-9cc6-49124d01a884", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial-dual0", + "tag": "morphism" + }, + "id": "019eb294-5186-76ff-9cc6-4f95974a1e1c", + "tag": "formal" + }, + "019eb294-d8bd-7508-b27d-1e04ae68cf36": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb294-d8bd-7508-b27d-18ae5f93db80", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "eq-dual0", + "tag": "morphism" + }, + "id": "019eb294-d8bd-7508-b27d-1e04ae68cf36", + "tag": "formal" + }, + "019eb748-0792-7608-a9af-ec91ac591709": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb748-0792-7608-a9af-e803e3886a10", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "subtract-0dual0", + "tag": "morphism" + }, + "id": "019eb748-0792-7608-a9af-ec91ac591709", + "tag": "formal" + }, + "019eb74c-be64-7701-826b-59b3669a1047": { + "content": { + "cod": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb74c-be64-7701-826b-54c6b28f28d9", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "lie_1d0", + "tag": "morphism" + }, + "id": "019eb74c-be64-7701-826b-59b3669a1047", + "tag": "formal" + } + }, + "cellOrder": [ + "019df3f4-8444-70e8-b8a3-6eff98e1c9d4", + "019df509-56a6-7656-98e6-8f4da9c812c9", + "019df509-6110-70d2-91fd-dd2a8dade102", + "019df3f4-3ec3-7069-94bd-60cf0befac08", + "019df509-6978-72bf-9362-13b7ad373887", + "019df3f4-7663-7519-ba02-a2826c5d8708", + "019df920-3a9a-70c4-821e-ac6de6a10c70", + "019eb28d-f5e6-777f-aae0-cf98e6ba85d2", + "019eb293-c69f-7049-ae1f-1f6bacfca350", + "019eb290-9d68-719b-8c67-c684659a091e", + "019eb748-0792-7608-a9af-ec91ac591709", + "019eb28f-792e-7414-807f-4151fa624da8", + "019eb28e-b02f-754a-b7ec-802d65be072e", + "019df91e-8033-716b-8526-1414019c5649", + "019eb290-1d17-720f-855d-5310d112eea2", + "019df50c-6981-7248-8a82-b42acdb471f0", + "019df509-e051-77aa-9899-df703a8c58cf", + "019df50a-6e39-7216-9b89-8a070cefc041", + "019df50b-da40-7399-9c85-0f8ae1241f0d", + "019df50d-0c28-71f9-bcc4-69bbe9ca152e", + "019df508-fe6a-751c-92e0-f04f0cf69a1a", + "019df526-a755-75f8-8528-f74ced38a971", + "019df526-5735-724b-b232-1a1aafd58233", + "019df509-9301-77d7-bdd5-43a3d908fbb8", + "019eb294-5186-76ff-9cc6-4f95974a1e1c", + "019df528-97dd-75f8-b9fd-c193b73d0b21", + "019df522-b423-7419-880c-82c4601bf9c4", + "019df5ea-62d9-761e-8c9c-9fc2629f83f0", + "019eb294-d8bd-7508-b27d-1e04ae68cf36", + "019eb74c-be64-7701-826b-59b3669a1047" + ] + }, + "theory": "dec", + "type": "model", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json b/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json new file mode 100644 index 000000000..7c2a1403d --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json @@ -0,0 +1,485 @@ +{ + "diagramIn": { + "_id": "019df1c9-1c72-72d3-a0f7-9de7af7db5d5", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "diagram-in" + }, + "name": "Phytodynamics", + "notebook": { + "cellContents": { + "019eb289-de33-7179-8cb8-53c018616fdd": { + "content": { + "id": "019eb289-de33-7179-8cb8-4d3ec8facbea", + "name": "w", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb289-de33-7179-8cb8-53c018616fdd", + "tag": "formal" + }, + "019eb28c-ceed-7781-a89e-ca6cefa6778f": { + "content": { + "id": "019eb28c-ceec-779a-b09d-67307cb2d5c2", + "name": "n", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28c-ceed-7781-a89e-ca6cefa6778f", + "tag": "formal" + }, + "019eb28c-eadb-7449-b64d-8018dbc2b6dc": { + "content": { + "id": "019eb28c-eadb-7449-b64d-7dcaf19df3f8", + "name": "m", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28c-eadb-7449-b64d-8018dbc2b6dc", + "tag": "formal" + }, + "019eb28c-ffb3-709f-a464-099dabf466b2": { + "content": { + "id": "019eb28c-ffb3-709f-a464-04c45a151e79", + "name": "y0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28c-ffb3-709f-a464-099dabf466b2", + "tag": "formal" + }, + "019eb28d-1a94-74ab-aad3-d1bd254c7211": { + "content": { + "id": "019eb28d-1a94-74ab-aad3-cd466479aaa9", + "name": "y1", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-1a94-74ab-aad3-d1bd254c7211", + "tag": "formal" + }, + "019eb28d-2ff3-71ae-940a-304b110b6611": { + "content": { + "id": "019eb28d-2ff3-71ae-940a-2dc597614a90", + "name": "y2", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-2ff3-71ae-940a-304b110b6611", + "tag": "formal" + }, + "019eb28d-45bb-773f-ac27-13cbae772c08": { + "content": { + "id": "019eb28d-45bb-773f-ac27-0ea5bebd6cc3", + "name": "y3", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-45bb-773f-ac27-13cbae772c08", + "tag": "formal" + }, + "019eb28d-557f-702c-96e5-46bd58ceb704": { + "content": { + "id": "019eb28d-557f-702c-96e5-407455924e70", + "name": "y4", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-557f-702c-96e5-46bd58ceb704", + "tag": "formal" + }, + "019eb28d-632c-75f7-a8d1-2e96641dc914": { + "content": { + "id": "019eb28d-632b-77ae-b839-21f5750732cf", + "name": "y5", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-632c-75f7-a8d1-2e96641dc914", + "tag": "formal" + }, + "019eb28d-7404-7026-8ff7-5deea875906f": { + "content": { + "id": "019eb28d-7404-7026-8ff7-59f9d26bdc60", + "name": "y6", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019eb28d-7404-7026-8ff7-5deea875906f", + "tag": "formal" + }, + "019eb28d-93dd-7415-a65b-ff74935aa1cc": { + "content": { + "cod": { + "content": "019eb28c-ffb3-709f-a464-04c45a151e79", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28c-ceec-779a-b09d-67307cb2d5c2", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28d-93dd-7415-a65b-fb11d1604825", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28e-b02f-754a-b7ec-7c9df716cac4", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb28d-93dd-7415-a65b-ff74935aa1cc", + "tag": "formal" + }, + "019eb28f-24f6-777e-91e0-c64a037797b6": { + "content": { + "cod": { + "content": "019eb28d-1a94-74ab-aad3-cd466479aaa9", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb289-de33-7179-8cb8-4d3ec8facbea", + "tag": "Basic" + }, + { + "content": "019eb28c-ffb3-709f-a464-04c45a151e79", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28f-24f5-77a7-9f54-d7f17152a8c7", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28d-f5e6-777f-aae0-cb7d08bd35a1", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb28f-24f6-777e-91e0-c64a037797b6", + "tag": "formal" + }, + "019eb28f-c94d-74ed-83e3-cb3cb176279c": { + "content": { + "cod": { + "content": "019eb28d-2ff3-71ae-940a-2dc597614a90", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28c-eadb-7449-b64d-7dcaf19df3f8", + "tag": "Basic" + }, + { + "content": "019eb28c-ceec-779a-b09d-67307cb2d5c2", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb28f-c94d-74ed-83e3-c553870c4524", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb28f-792e-7414-807f-3f3ba6138ed7", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb28f-c94d-74ed-83e3-cb3cb176279c", + "tag": "formal" + }, + "019eb290-42ed-759b-acb2-6b4426897467": { + "content": { + "cod": { + "content": "019eb28d-45bb-773f-ac27-0ea5bebd6cc3", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28d-1a94-74ab-aad3-cd466479aaa9", + "tag": "Basic" + }, + { + "content": "019eb28d-2ff3-71ae-940a-2dc597614a90", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb290-42ed-759b-acb2-65f2ab40a42d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb290-9d67-769e-a374-775d25df2eed", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb290-42ed-759b-acb2-6b4426897467", + "tag": "formal" + }, + "019eb293-832d-748f-8ef9-cb5abd7cc60a": { + "content": { + "cod": { + "content": "019eb28d-557f-702c-96e5-407455924e70", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28c-ceec-779a-b09d-67307cb2d5c2", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb293-832d-748f-8ef9-c76852cec2d4", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb290-1d17-720f-855d-4d03eb5d79d1", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb293-832d-748f-8ef9-cb5abd7cc60a", + "tag": "formal" + }, + "019eb293-fe05-744c-8d77-570fe94d3515": { + "content": { + "cod": { + "content": "019eb28d-632b-77ae-b839-21f5750732cf", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28d-45bb-773f-ac27-0ea5bebd6cc3", + "tag": "Basic" + }, + { + "content": "019eb28d-557f-702c-96e5-407455924e70", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb293-fe05-744c-8d77-5240e4200dc9", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb293-c69e-75eb-b499-a8a62a67e559", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb293-fe05-744c-8d77-570fe94d3515", + "tag": "formal" + }, + "019eb294-887d-745c-bae2-865a152cd301": { + "content": { + "cod": { + "content": "019eb28d-7404-7026-8ff7-59f9d26bdc60", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb289-de33-7179-8cb8-4d3ec8facbea", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb294-887d-745c-bae2-817b2fa1fba6", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb294-5186-76ff-9cc6-49124d01a884", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb294-887d-745c-bae2-865a152cd301", + "tag": "formal" + }, + "019eb295-1cc8-708b-be15-52de425b3b91": { + "content": { + "cod": { + "content": "019eb28d-632b-77ae-b839-21f5750732cf", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019eb28d-7404-7026-8ff7-59f9d26bdc60", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019eb295-1cc8-708b-be15-4fd233642073", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019eb294-d8bd-7508-b27d-18ae5f93db80", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019eb295-1cc8-708b-be15-52de425b3b91", + "tag": "formal" + } + }, + "cellOrder": [ + "019eb289-de33-7179-8cb8-53c018616fdd", + "019eb28c-ceed-7781-a89e-ca6cefa6778f", + "019eb28c-eadb-7449-b64d-8018dbc2b6dc", + "019eb28c-ffb3-709f-a464-099dabf466b2", + "019eb28d-1a94-74ab-aad3-d1bd254c7211", + "019eb28d-2ff3-71ae-940a-304b110b6611", + "019eb28d-45bb-773f-ac27-13cbae772c08", + "019eb28d-557f-702c-96e5-46bd58ceb704", + "019eb28d-632c-75f7-a8d1-2e96641dc914", + "019eb28d-7404-7026-8ff7-5deea875906f", + "019eb28d-93dd-7415-a65b-ff74935aa1cc", + "019eb28f-24f6-777e-91e0-c64a037797b6", + "019eb28f-c94d-74ed-83e3-cb3cb176279c", + "019eb290-42ed-759b-acb2-6b4426897467", + "019eb293-832d-748f-8ef9-cb5abd7cc60a", + "019eb293-fe05-744c-8d77-570fe94d3515", + "019eb294-887d-745c-bae2-865a152cd301", + "019eb295-1cc8-708b-be15-52de425b3b91" + ] + }, + "type": "diagram", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/ns_vorticity/model_dec_fragment.json b/packages/catlog/examples/tt/notebook/ns_vorticity/model_dec_fragment.json new file mode 100644 index 000000000..dc5e42864 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/ns_vorticity/model_dec_fragment.json @@ -0,0 +1,500 @@ +{ + "name": "DEC Fragment", + "notebook": { + "cellContents": { + "019df3f4-3ec3-7069-94bd-60cf0befac08": { + "content": { + "id": "019df3f4-3ec3-7069-94bd-5f5097133079", + "name": "Dual 0-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-3ec3-7069-94bd-60cf0befac08", + "tag": "formal" + }, + "019df3f4-7663-7519-ba02-a2826c5d8708": { + "content": { + "id": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "name": "Dual 2-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-7663-7519-ba02-a2826c5d8708", + "tag": "formal" + }, + "019df3f4-8444-70e8-b8a3-6eff98e1c9d4": { + "content": { + "id": "019df3f4-8443-71d6-a106-e211b1f1a359", + "name": "0-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df3f4-8444-70e8-b8a3-6eff98e1c9d4", + "tag": "formal" + }, + "019df508-fe6a-751c-92e0-f04f0cf69a1a": { + "content": { + "cod": { + "content": "019df509-6110-70d2-91fd-d81a3b4eab88", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df508-fe6a-751c-92e0-ed1ffccbfb20", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "0-inv-star", + "tag": "morphism" + }, + "id": "019df508-fe6a-751c-92e0-f04f0cf69a1a", + "tag": "formal" + }, + "019df509-56a6-7656-98e6-8f4da9c812c9": { + "content": { + "id": "019df509-56a6-7656-98e6-896813fa1052", + "name": "1-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-56a6-7656-98e6-8f4da9c812c9", + "tag": "formal" + }, + "019df509-6110-70d2-91fd-dd2a8dade102": { + "content": { + "id": "019df509-6110-70d2-91fd-d81a3b4eab88", + "name": "2-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-6110-70d2-91fd-dd2a8dade102", + "tag": "formal" + }, + "019df509-6978-72bf-9362-13b7ad373887": { + "content": { + "id": "019df509-6978-72bf-9362-0e085ed78843", + "name": "Dual 1-Form", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df509-6978-72bf-9362-13b7ad373887", + "tag": "formal" + }, + "019df509-9301-77d7-bdd5-43a3d908fbb8": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df509-9301-77d7-bdd5-3ffaea8a24a1", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial", + "tag": "morphism" + }, + "id": "019df509-9301-77d7-bdd5-43a3d908fbb8", + "tag": "formal" + }, + "019df509-e051-77aa-9899-df703a8c58cf": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df509-e051-77aa-9899-db60385d3b57", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "0-d", + "tag": "morphism" + }, + "id": "019df509-e051-77aa-9899-df703a8c58cf", + "tag": "formal" + }, + "019df50a-6e39-7216-9b89-8a070cefc041": { + "content": { + "cod": { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50a-6e39-7216-9b89-87168f762a5c", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "1-star", + "tag": "morphism" + }, + "id": "019df50a-6e39-7216-9b89-8a070cefc041", + "tag": "formal" + }, + "019df50b-da40-7399-9c85-0f8ae1241f0d": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50b-da40-7399-9c85-094a02bde699", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "sharp-flat", + "tag": "morphism" + }, + "id": "019df50b-da40-7399-9c85-0f8ae1241f0d", + "tag": "formal" + }, + "019df50c-6981-7248-8a82-b42acdb471f0": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50c-6981-7248-8a82-b1da5d63ab63", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "inv-laplace", + "tag": "morphism" + }, + "id": "019df50c-6981-7248-8a82-b42acdb471f0", + "tag": "formal" + }, + "019df50d-0c28-71f9-bcc4-69bbe9ca152e": { + "content": { + "cod": { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-3ec3-7069-94bd-5f5097133079", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df50d-0c28-71f9-bcc4-643ca621044d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dual 0-d", + "tag": "morphism" + }, + "id": "019df50d-0c28-71f9-bcc4-69bbe9ca152e", + "tag": "formal" + }, + "019df522-b423-7419-880c-82c4601bf9c4": { + "content": { + "cod": { + "content": "019df509-56a6-7656-98e6-896813fa1052", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df522-b423-7419-880c-7c8b6f62eb38", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dp-wedge-10", + "tag": "morphism" + }, + "id": "019df522-b423-7419-880c-82c4601bf9c4", + "tag": "formal" + }, + "019df526-5735-724b-b232-1a1aafd58233": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df526-5735-724b-b232-140c13a4a438", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "2-inv-star", + "tag": "morphism" + }, + "id": "019df526-5735-724b-b232-1a1aafd58233", + "tag": "formal" + }, + "019df526-a755-75f8-8528-f74ced38a971": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df526-a755-75f8-8528-f381f4feec4d", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "dual 1-d", + "tag": "morphism" + }, + "id": "019df526-a755-75f8-8528-f74ced38a971", + "tag": "formal" + }, + "019df528-97dd-75f8-b9fd-c193b73d0b21": { + "content": "I assume this is correct...", + "id": "019df528-97dd-75f8-b9fd-c193b73d0b21", + "tag": "rich-text" + }, + "019df5ea-62d9-761e-8c9c-9fc2629f83f0": { + "content": { + "cod": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5ea-62d9-761e-8c9c-9b5cf73e7240", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "-", + "tag": "morphism" + }, + "id": "019df5ea-62d9-761e-8c9c-9fc2629f83f0", + "tag": "formal" + }, + "019df91e-8033-716b-8526-1414019c5649": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df91e-8033-716b-8526-12e08e9bec59", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "laplace", + "tag": "morphism" + }, + "id": "019df91e-8033-716b-8526-1414019c5649", + "tag": "formal" + }, + "019df920-3a9a-70c4-821e-ac6de6a10c70": { + "content": { + "cod": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df920-3a9a-70c4-821e-a871751e58ec", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "multiplication", + "tag": "morphism" + }, + "id": "019df920-3a9a-70c4-821e-ac6de6a10c70", + "tag": "formal" + } + }, + "cellOrder": [ + "019df3f4-8444-70e8-b8a3-6eff98e1c9d4", + "019df509-56a6-7656-98e6-8f4da9c812c9", + "019df509-6110-70d2-91fd-dd2a8dade102", + "019df3f4-3ec3-7069-94bd-60cf0befac08", + "019df509-6978-72bf-9362-13b7ad373887", + "019df3f4-7663-7519-ba02-a2826c5d8708", + "019df920-3a9a-70c4-821e-ac6de6a10c70", + "019df91e-8033-716b-8526-1414019c5649", + "019df50c-6981-7248-8a82-b42acdb471f0", + "019df509-e051-77aa-9899-df703a8c58cf", + "019df50a-6e39-7216-9b89-8a070cefc041", + "019df50b-da40-7399-9c85-0f8ae1241f0d", + "019df50d-0c28-71f9-bcc4-69bbe9ca152e", + "019df508-fe6a-751c-92e0-f04f0cf69a1a", + "019df526-a755-75f8-8528-f74ced38a971", + "019df526-5735-724b-b232-1a1aafd58233", + "019df509-9301-77d7-bdd5-43a3d908fbb8", + "019df528-97dd-75f8-b9fd-c193b73d0b21", + "019df522-b423-7419-880c-82c4601bf9c4", + "019df5ea-62d9-761e-8c9c-9fc2629f83f0" + ] + }, + "theory": "dec", + "type": "model", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/ns_vorticity/ns_vorticity.json b/packages/catlog/examples/tt/notebook/ns_vorticity/ns_vorticity.json new file mode 100644 index 000000000..cd3eeec1a --- /dev/null +++ b/packages/catlog/examples/tt/notebook/ns_vorticity/ns_vorticity.json @@ -0,0 +1,483 @@ +{ + "diagramIn": { + "_id": "019df1c9-1c72-72d3-a0f7-9de7af7db5d5", + "_server": "backend-next.catcolab.org", + "_version": null, + "type": "diagram-in" + }, + "name": "NS Vorticity", + "notebook": { + "cellContents": { + "019df529-6dbd-70ad-b1bb-452cb63e81ef": { + "content": { + "id": "019df529-6dbc-74ef-b09b-e9033b1cd729", + "name": "v", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df509-6978-72bf-9362-0e085ed78843", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df529-6dbd-70ad-b1bb-452cb63e81ef", + "tag": "formal" + }, + "019df529-8da6-763a-b0b6-f0fcca704e74": { + "content": { + "id": "019df529-8da6-763a-b0b6-ef7faa28e8f2", + "name": "dv", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-7663-7519-ba02-9cadb41b87f0", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df529-8da6-763a-b0b6-f0fcca704e74", + "tag": "formal" + }, + "019df529-ab54-760c-a5d6-d5417c8ad548": { + "content": { + "id": "019df529-ab54-760c-a5d6-d16cd2905323", + "name": "psi", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "over": { + "content": "019df3f4-8443-71d6-a106-e211b1f1a359", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019df529-ab54-760c-a5d6-d5417c8ad548", + "tag": "formal" + }, + "019df53a-d21a-76b8-a969-a907758040d7": { + "content": { + "cod": { + "content": "019df5e1-f2ff-7458-9b18-7552e4ed2411", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df529-8da6-763a-b0b6-ef7faa28e8f2", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df53a-d21a-76b8-a969-a408c47360a3", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df508-fe6a-751c-92e0-ed1ffccbfb20", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df53a-d21a-76b8-a969-a907758040d7", + "tag": "formal" + }, + "019df5e1-fe53-737f-ae36-9f9724456c7f": { + "content": { + "cod": { + "content": "019df529-ab54-760c-a5d6-d16cd2905323", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e1-f2ff-7458-9b18-7552e4ed2411", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e1-fe53-737f-ae36-9a8a537486c7", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df50c-6981-7248-8a82-b1da5d63ab63", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e1-fe53-737f-ae36-9f9724456c7f", + "tag": "formal" + }, + "019df5e5-af2b-7008-9f4c-db1a24416d55": { + "content": { + "cod": { + "content": "019df5e5-c238-72a0-bfd8-07e43d5877bc", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df529-ab54-760c-a5d6-d16cd2905323", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e5-af2b-7008-9f4c-d6019e3131f4", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df509-e051-77aa-9899-db60385d3b57", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e5-af2b-7008-9f4c-db1a24416d55", + "tag": "formal" + }, + "019df5e5-dc48-73d8-80ff-b0696f8fcfc7": { + "content": { + "cod": { + "content": "019df529-6dbc-74ef-b09b-e9033b1cd729", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e5-c238-72a0-bfd8-07e43d5877bc", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e5-dc48-73d8-80ff-adce21a06334", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df50a-6e39-7216-9b89-87168f762a5c", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e5-dc48-73d8-80ff-b0696f8fcfc7", + "tag": "formal" + }, + "019df5e6-1b54-7791-b6d6-2dd1f0f34ca6": { + "content": { + "cod": { + "content": "019df5e7-2dea-777b-beaf-1d649ae5dc63", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df529-6dbc-74ef-b09b-e9033b1cd729", + "tag": "Basic" + }, + { + "content": "019df5e7-cd6a-757d-989b-821888c27cc6", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e6-1b54-7791-b6d6-2b773528d386", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df522-b423-7419-880c-7c8b6f62eb38", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e6-1b54-7791-b6d6-2dd1f0f34ca6", + "tag": "formal" + }, + "019df5e6-7de5-72dd-a1ca-ce67fdb559d5": { + "content": { + "cod": { + "content": "019df5e7-3b4f-769e-8b78-4e7aa6ed6cca", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df529-6dbc-74ef-b09b-e9033b1cd729", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e6-7de5-72dd-a1ca-c94fec27e219", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df526-a755-75f8-8528-f381f4feec4d", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e6-7de5-72dd-a1ca-ce67fdb559d5", + "tag": "formal" + }, + "019df5e7-54dd-73a3-9d4d-fe96f8f88e34": { + "content": { + "cod": { + "content": "019df5e7-cd6a-757d-989b-821888c27cc6", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e7-3b4f-769e-8b78-4e7aa6ed6cca", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e7-54dd-73a3-9d4d-fb851fab23eb", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df508-fe6a-751c-92e0-ed1ffccbfb20", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e7-54dd-73a3-9d4d-fe96f8f88e34", + "tag": "formal" + }, + "019df5e8-04c5-76a7-94f0-f3ba870ea0a4": { + "content": { + "cod": { + "content": "019df5e8-1e54-742a-a46b-e67afce0c7b9", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e7-2dea-777b-beaf-1d649ae5dc63", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e8-04c5-76a7-94f0-ecd7e73b9b5a", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df50b-da40-7399-9c85-094a02bde699", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e8-04c5-76a7-94f0-f3ba870ea0a4", + "tag": "formal" + }, + "019df5e8-3257-7148-b61b-4022cead3750": { + "content": { + "cod": { + "content": "019df5e9-1ff1-76be-876c-9ca1ff79c322", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df529-8da6-763a-b0b6-ef7faa28e8f2", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e8-3257-7148-b61b-3d04eb245d60", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df509-9301-77d7-bdd5-3ffaea8a24a1", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e8-3257-7148-b61b-4022cead3750", + "tag": "formal" + }, + "019df5e8-a1a0-7387-ba3d-4c14e3c0d09c": { + "content": { + "cod": { + "content": "019df5e8-d2b9-7747-a43b-13c7b6c6a0ff", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e8-1e54-742a-a46b-e67afce0c7b9", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e8-a1a0-7387-ba3d-4a014db3e8dc", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df50a-6e39-7216-9b89-87168f762a5c", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e8-a1a0-7387-ba3d-4c14e3c0d09c", + "tag": "formal" + }, + "019df5e9-3517-729b-8ead-9ff927e40a12": { + "content": { + "cod": { + "content": "019df5ea-23eb-714f-aceb-d3c8130fb2a3", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5e8-d2b9-7747-a43b-13c7b6c6a0ff", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5e9-3517-729b-8ead-98793f1ab691", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df526-a755-75f8-8528-f381f4feec4d", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5e9-3517-729b-8ead-9ff927e40a12", + "tag": "formal" + }, + "019df5ea-2af4-709f-8807-fcfccfdd5cdc": { + "content": { + "cod": { + "content": "019df5e9-1ff1-76be-876c-9ca1ff79c322", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "List", + "objects": [ + { + "content": "019df5ea-23eb-714f-aceb-d3c8130fb2a3", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "019df5ea-2af4-709f-8807-f8981a83d190", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "", + "over": { + "content": "019df5ea-62d9-761e-8c9c-9b5cf73e7240", + "tag": "Basic" + }, + "tag": "morphism" + }, + "id": "019df5ea-2af4-709f-8807-fcfccfdd5cdc", + "tag": "formal" + } + }, + "cellOrder": [ + "019df529-6dbd-70ad-b1bb-452cb63e81ef", + "019df529-8da6-763a-b0b6-f0fcca704e74", + "019df529-ab54-760c-a5d6-d5417c8ad548", + "019df53a-d21a-76b8-a969-a907758040d7", + "019df5e1-fe53-737f-ae36-9f9724456c7f", + "019df5e5-af2b-7008-9f4c-db1a24416d55", + "019df5e5-dc48-73d8-80ff-b0696f8fcfc7", + "019df5e6-7de5-72dd-a1ca-ce67fdb559d5", + "019df5e7-54dd-73a3-9d4d-fe96f8f88e34", + "019df5e6-1b54-7791-b6d6-2dd1f0f34ca6", + "019df5e8-04c5-76a7-94f0-f3ba870ea0a4", + "019df5e8-a1a0-7387-ba3d-4c14e3c0d09c", + "019df5e8-3257-7148-b61b-4022cead3750", + "019df5e9-3517-729b-8ead-9ff927e40a12", + "019df5ea-2af4-709f-8807-fcfccfdd5cdc" + ] + }, + "type": "diagram", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/sir_petri.json b/packages/catlog/examples/tt/notebook/sir_petri.json index aea520347..16ef8b07d 100644 --- a/packages/catlog/examples/tt/notebook/sir_petri.json +++ b/packages/catlog/examples/tt/notebook/sir_petri.json @@ -1 +1,180 @@ -{"name":"SIR","notebook":{"cellContents":{"019c34ca-5c0e-77af-88e3-c7ae9e1936cc":{"content":{"id":"019c34ca-5c0e-77af-88e3-c2d4487cadaf","name":"S","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-5c0e-77af-88e3-c7ae9e1936cc","tag":"formal"},"019c34ca-8090-7566-9db3-49f54b5d7eae":{"content":{"id":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","name":"I","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-8090-7566-9db3-49f54b5d7eae","tag":"formal"},"019c34ca-83b0-70f9-a861-663b422d3109":{"content":{"id":"019c34ca-83b0-70f9-a861-61ad91bdd5b6","name":"R","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-83b0-70f9-a861-663b422d3109","tag":"formal"},"019c34ca-92a8-702b-b09b-f711a18563b8":{"content":{"cod":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"},{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"dom":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-5c0e-77af-88e3-c2d4487cadaf","tag":"Basic"},{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"id":"019c34ca-92a8-702b-b09b-f303b34fddde","morType":{"content":{"content":"Object","tag":"Basic"},"tag":"Hom"},"name":"infect","tag":"morphism"},"id":"019c34ca-92a8-702b-b09b-f711a18563b8","tag":"formal"},"019c34ca-a808-7119-8682-626850b293e2":{"content":{"cod":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-83b0-70f9-a861-61ad91bdd5b6","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"dom":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"id":"019c34ca-a808-7119-8682-5d964079256e","morType":{"content":{"content":"Object","tag":"Basic"},"tag":"Hom"},"name":"recover","tag":"morphism"},"id":"019c34ca-a808-7119-8682-626850b293e2","tag":"formal"}},"cellOrder":["019c34ca-5c0e-77af-88e3-c7ae9e1936cc","019c34ca-8090-7566-9db3-49f54b5d7eae","019c34ca-83b0-70f9-a861-663b422d3109","019c34ca-92a8-702b-b09b-f711a18563b8","019c34ca-a808-7119-8682-626850b293e2"]},"theory":"petri-net","type":"model","version":"1"} \ No newline at end of file +{ + "name": "SIR", + "notebook": { + "cellContents": { + "019c34ca-5c0e-77af-88e3-c7ae9e1936cc": { + "content": { + "id": "019c34ca-5c0e-77af-88e3-c2d4487cadaf", + "name": "S", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019c34ca-5c0e-77af-88e3-c7ae9e1936cc", + "tag": "formal" + }, + "019c34ca-8090-7566-9db3-49f54b5d7eae": { + "content": { + "id": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "name": "I", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019c34ca-8090-7566-9db3-49f54b5d7eae", + "tag": "formal" + }, + "019c34ca-83b0-70f9-a861-663b422d3109": { + "content": { + "id": "019c34ca-83b0-70f9-a861-61ad91bdd5b6", + "name": "R", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "019c34ca-83b0-70f9-a861-663b422d3109", + "tag": "formal" + }, + "019c34ca-92a8-702b-b09b-f711a18563b8": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + }, + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-5c0e-77af-88e3-c2d4487cadaf", + "tag": "Basic" + }, + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019c34ca-92a8-702b-b09b-f303b34fddde", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "infect", + "tag": "morphism" + }, + "id": "019c34ca-92a8-702b-b09b-f711a18563b8", + "tag": "formal" + }, + "019c34ca-a808-7119-8682-626850b293e2": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-83b0-70f9-a861-61ad91bdd5b6", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { + "content": "tensor", + "tag": "Basic" + } + }, + "tag": "App" + }, + "id": "019c34ca-a808-7119-8682-5d964079256e", + "morType": { + "content": { + "content": "Object", + "tag": "Basic" + }, + "tag": "Hom" + }, + "name": "recover", + "tag": "morphism" + }, + "id": "019c34ca-a808-7119-8682-626850b293e2", + "tag": "formal" + } + }, + "cellOrder": [ + "019c34ca-5c0e-77af-88e3-c7ae9e1936cc", + "019c34ca-8090-7566-9db3-49f54b5d7eae", + "019c34ca-83b0-70f9-a861-663b422d3109", + "019c34ca-92a8-702b-b09b-f711a18563b8", + "019c34ca-a808-7119-8682-626850b293e2" + ] + }, + "theory": "petri-net", + "type": "model", + "version": "1" +} diff --git a/packages/catlog/examples/tt/text/test_instances.dbltt b/packages/catlog/examples/tt/text/test_instances.dbltt new file mode 100644 index 000000000..9f9857a9e --- /dev/null +++ b/packages/catlog/examples/tt/text/test_instances.dbltt @@ -0,0 +1,28 @@ +set_theory ThSchema + +type WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] + +diagram I : @Instance(WeightedGraph) := [ + e : @over .E, +] + +#/ Should there be some sugar for this situation, where I'm basically just renaming default-named guys? +diagram I2 : @Instance(WeightedGraph) := [ + v1 : @over .V, + v2 : @over .V, + w : @over .Weight, + we : I & [.src(e) := v1, .tgt(e) := v2, .weight(e) := w], + wf : I & [.src(e) := v2, .tgt(e) := v1, .weight(e) := w] +] + +diagram I3 : @Instance(WeightedGraph) := [ + e1 : @over .E, + e2 : @over .E +] \ No newline at end of file diff --git a/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot b/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot new file mode 100644 index 000000000..8a5c474e7 --- /dev/null +++ b/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot @@ -0,0 +1,71 @@ +set_theory ThSchema +#/ result: set theory to ThSchema + +type WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] +#/ declared: WeightedGraph + +diagram I : @Instance(WeightedGraph) := [ + e : @over .E, +] +#/ declared: I +#/ diagram domain: +#/ e : Entity -> E +#/ src(e) : Entity -> V +#/ tgt(e) : Entity -> V +#/ weight(e) : AttrType -> Weight +#/ ~src(e) : Hom(Entity)[e, src(e)] -> src +#/ ~tgt(e) : Hom(Entity)[e, tgt(e)] -> tgt +#/ ~weight(e) : Attr[e, weight(e)] -> weight +#/ diagram validates against codomain + +diagram I2 : @Instance(WeightedGraph) := [ + v1 : @over .V, + v2 : @over .V, + w : @over .Weight, + we : I & [.src(e) := v1, .tgt(e) := v2, .weight(e) := w], + wf : I & [.src(e) := v2, .tgt(e) := v1, .weight(e) := w] +] +#/ declared: I2 +#/ diagram domain: +#/ v1 : Entity -> V +#/ v2 : Entity -> V +#/ w : AttrType -> Weight +#/ we.e : Entity -> E +#/ wf.e : Entity -> E +#/ we.~src(e) : Hom(Entity)[we.e, v1] -> src +#/ we.~tgt(e) : Hom(Entity)[we.e, v2] -> tgt +#/ we.~weight(e) : Attr[we.e, w] -> weight +#/ wf.~src(e) : Hom(Entity)[wf.e, v2] -> src +#/ wf.~tgt(e) : Hom(Entity)[wf.e, v1] -> tgt +#/ wf.~weight(e) : Attr[wf.e, w] -> weight +#/ diagram validates against codomain + +diagram I3 : @Instance(WeightedGraph) := [ + e1 : @over .E, + e2 : @over .E +] +#/ declared: I3 +#/ diagram domain: +#/ e1 : Entity -> E +#/ e2 : Entity -> E +#/ src(e1) : Entity -> V +#/ tgt(e1) : Entity -> V +#/ weight(e1) : AttrType -> Weight +#/ src(e2) : Entity -> V +#/ tgt(e2) : Entity -> V +#/ weight(e2) : AttrType -> Weight +#/ ~src(e1) : Hom(Entity)[e1, src(e1)] -> src +#/ ~tgt(e1) : Hom(Entity)[e1, tgt(e1)] -> tgt +#/ ~weight(e1) : Attr[e1, weight(e1)] -> weight +#/ ~src(e2) : Hom(Entity)[e2, src(e2)] -> src +#/ ~tgt(e2) : Hom(Entity)[e2, tgt(e2)] -> tgt +#/ ~weight(e2) : Attr[e2, weight(e2)] -> weight +#/ diagram validates against codomain + diff --git a/packages/catlog/examples/tt/text/test_klausmeier.dbltt b/packages/catlog/examples/tt/text/test_klausmeier.dbltt new file mode 100644 index 000000000..fc39b0944 --- /dev/null +++ b/packages/catlog/examples/tt/text/test_klausmeier.dbltt @@ -0,0 +1,72 @@ +set_theory ThMulticategory + +type DEC := [ + Form0 : Object, + Form1 : Object, + DualForm0 : Object, + lapl_d0 : Multihom[[DualForm0], DualForm0], + partial_0 : Multihom[[Form0], Form0], + partial_1 : Multihom[[Form1], Form1], + partial_d0 : Multihom[[DualForm0], DualForm0], + square_d0 : Multihom[[DualForm0], DualForm0], + add_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + sub_d01 : Multihom[[DualForm0, Form0], DualForm0], + sub_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_00 : Multihom[[Form0, Form0], Form0], + mult_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_0d0 : Multihom[[Form0, DualForm0], DualForm0], + lie_1d0 : Multihom[[Form1, DualForm0], DualForm0], + wedge_00: Multihom[[Form0, Form0], Form0], + wedge_10: Multihom[[Form1, Form0], Form1] +] + +diagram Hydrodynamics : @Instance(DEC) := [ + a : @over .Form0, + k : @over .Form0, + n : @over .DualForm0, + w : @over .DualForm0, + dX : @over .Form1, + x0 : @over .DualForm0, + x1 : @over .DualForm0, + x2 : @over .DualForm0, + x3 : @over .DualForm0, + x4 : @over .DualForm0, + x5 : @over .DualForm0, + x6 : @over .DualForm0, + _1 : ( x0 == sub_d01([w,a]) ), + _2 : ( x1 == square_d0([n]) ), + _3 : ( x2 == mult_d0d0([w, x1]) ), + _4 : ( x3 == sub_d0d0([x0, x2]) ), + _5 : ( x4 == lie_1d0([dX, w]) ), + _6 : ( x5 == mult_0d0([k, x4]) ), + _7 : ( x6 == add_d0d0([x3, x5]) ), + _8 : ( x6 == partial_d0([w]) ) +] + +diagram Phytodynamics : @Instance(DEC) := [ + n : @over .DualForm0, + w : @over .DualForm0, + m : @over .Form0, + y0 : @over .DualForm0, + y1 : @over .DualForm0, + y2 : @over .DualForm0, + y3 : @over .DualForm0, + y4 : @over .DualForm0, + y5 : @over .DualForm0, + y6 : @over .DualForm0, + _1 : ( y0 == square_d0([n]) ), + _2 : ( y1 == mult_d0d0([w, y0]) ), + _3 : ( y2 == mult_0d0([m, n]) ), + _4 : ( y3 == sub_d0d0([y1, y2]) ), + _5 : ( y4 == lapl_d0([n]) ), + _6 : ( y5 == add_d0d0([y3, y4]) ), + _7 : ( y6 == partial_d0([w]) ), + _8 : ( y6 == y5 ) +] + +diagram Klausmeier : @Instance(DEC) := [ + hydro : Hydrodynamics, + phyto : Phytodynamics, + _1 : ( hydro.n == phyto.n ), + _2 : ( hydro.w == phyto.w ) +] diff --git a/packages/catlog/src/dbl/discrete/model_diagram.rs b/packages/catlog/src/dbl/discrete/model_diagram.rs index 24c26b0f2..c0c401560 100644 --- a/packages/catlog/src/dbl/discrete/model_diagram.rs +++ b/packages/catlog/src/dbl/discrete/model_diagram.rs @@ -6,7 +6,9 @@ use nonempty::NonEmpty; #[cfg(feature = "serde-wasm")] use tsify::declare; -use crate::dbl::{model::*, model_diagram::*, model_morphism::*}; +use crate::dbl::{ + discrete::DiscreteDblModelMapping, model::*, model_diagram::*, model_morphism::*, +}; use crate::one::{Category, FgCategory, GraphMapping}; use crate::validate; use crate::zero::{Mapping, QualifiedName}; @@ -19,11 +21,20 @@ pub type DiscreteDblModelDiagram = DblModelDiagram>; -impl DiscreteDblModelDiagram { +impl Diagram for DiscreteDblModelDiagram { + type Model = DiscreteDblModel; + type Mapping = DiscreteDblModelMapping; + type InvalidDiagram = InvalidDiscreteDblModelDiagram; + + /// Destructure + fn destructure(&self) -> (Self::Mapping, Self::Model) { + (self.0.clone(), self.1.clone()) + } + /// Validates that the diagram is well-defined in the given model. /// /// Assumes that the model is valid. If it is not, this function may panic. - pub fn validate_in( + fn validate_in( &self, model: &DiscreteDblModel, ) -> Result<(), NonEmpty> { @@ -31,7 +42,7 @@ impl DiscreteDblModelDiagram { } /// Iterates over failures of the diagram to be valid in the given model. - pub fn iter_invalid_in<'a>( + fn iter_invalid_in<'a>( &'a self, model: &'a DiscreteDblModel, ) -> impl Iterator + 'a { @@ -47,7 +58,7 @@ impl DiscreteDblModelDiagram { /// Infer missing data in the diagram from the model, where possible. /// /// Assumes that the model is valid. - pub fn infer_missing_from(&mut self, model: &DiscreteDblModel) { + fn infer_missing_from(&mut self, model: &DiscreteDblModel) { let (mapping, domain) = self.into(); domain.infer_missing(); for e in domain.mor_generators() { @@ -95,7 +106,7 @@ mod tests { domain.add_mor(name("f"), name("x"), name("y"), name("Attr").into()); let mut f: DiscreteDblModelMapping = Default::default(); f.assign_mor(name("f"), Path::single(name("attr"))); - let mut diagram = DblModelDiagram(f, domain); + let mut diagram = DblModelDiagram(f, domain.clone()); let model = walking_attr(th); diagram.infer_missing_from(&model); diff --git a/packages/catlog/src/dbl/discrete/model_morphism.rs b/packages/catlog/src/dbl/discrete/model_morphism.rs index eabd0e906..273b27674 100644 --- a/packages/catlog/src/dbl/discrete/model_morphism.rs +++ b/packages/catlog/src/dbl/discrete/model_morphism.rs @@ -15,17 +15,19 @@ use crate::zero::{HashColumn, Mapping, MutMapping, QualifiedName}; /// /// Because a discrete double theory has only trivial operations, the naturality /// axioms for a model morphism are also trivial. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct DiscreteDblModelMapping(pub DiscreteDblModelMappingData); +pub type DiscreteDblModelMapping = DblModelMapping; type DiscreteDblModelMappingData = FpFunctorData< HashColumn, HashColumn, >; -impl DiscreteDblModelMapping { +impl MutDblModelMapping for DiscreteDblModelMapping { + type ObGen = QualifiedName; + type MorGen = QualifiedPath; + /// Constructs a model mapping from a pair of hash maps. - pub fn new( + fn new( ob_pairs: impl IntoIterator, mor_pairs: impl IntoIterator, ) -> Self { @@ -36,33 +38,27 @@ impl DiscreteDblModelMapping { } /// Assigns an object generator, returning the previous assignment. - pub fn assign_ob(&mut self, x: QualifiedName, y: QualifiedName) -> Option { + fn assign_ob(&mut self, x: QualifiedName, y: QualifiedName) -> Option { self.0.ob_generator_map.set(x, y) } /// Assigns a morphism generator, returning the previous assignment. - pub fn assign_mor(&mut self, e: QualifiedName, n: QualifiedPath) -> Option { + fn assign_mor(&mut self, e: QualifiedName, n: QualifiedPath) -> Option { self.0.mor_generator_map.set(e, n) } /// Unassigns an object generator, returning the previous assignment. - pub fn unassign_ob(&mut self, x: &QualifiedName) -> Option { + fn unassign_ob(&mut self, x: &QualifiedName) -> Option { self.0.ob_generator_map.unset(x) } /// Unassigns a morphism generator, returning the previous assignment. - pub fn unassign_mor(&mut self, e: &QualifiedName) -> Option { + fn unassign_mor(&mut self, e: &QualifiedName) -> Option { self.0.mor_generator_map.unset(e) } +} - /// Interprets the data as a functor into the given model. - pub fn functor_into<'a>( - &'a self, - cod: &'a DiscreteDblModel, - ) -> FpFunctor<'a, DiscreteDblModelMappingData, QualifiedFpCategory> { - self.0.functor_into(&cod.category) - } - +impl DiscreteDblModelMapping { /// Finder of morphisms between two models of a discrete double theory. pub fn morphisms<'a>( dom: &'a DiscreteDblModel, @@ -70,14 +66,15 @@ impl DiscreteDblModelMapping { ) -> DiscreteDblModelMorphismFinder<'a> { DiscreteDblModelMorphismFinder::new(dom, cod) } -} -/// A functor between models of a double theory. -/// -/// This struct borrows its data to perform validation. The domain and codomain are -/// assumed to be valid models of double theories. If that is in question, the -/// models should be validated *before* validating this object. -pub struct DblModelMorphism<'a, Map, Dom, Cod>(pub &'a Map, pub &'a Dom, pub &'a Cod); + /// Interprets the data as a functor into the given model. + pub fn functor_into<'a>( + &'a self, + cod: &'a DiscreteDblModel, + ) -> FpFunctor<'a, DiscreteDblModelMappingData, QualifiedFpCategory> { + self.0.functor_into(&cod.category) + } +} /// A morphism between models of a discrete double theory. pub type DiscreteDblModelMorphism<'a> = @@ -89,7 +86,7 @@ impl<'a> DiscreteDblModelMorphism<'a> { &self, ) -> impl Iterator> + 'a + use<'a> { - let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, cod) = *self; + let DblModelMorphism(DblModelMapping(mapping), dom, cod) = *self; let category_errors: Vec<_> = mapping .functor_into(&cod.category) .iter_invalid_on(&dom.category) @@ -128,14 +125,14 @@ impl<'a> DiscreteDblModelMorphism<'a> { /// Are morphism generators sent to simple composites of morphisms in the /// codomain? fn is_simple(&self) -> bool { - let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, _) = *self; + let DblModelMorphism(DblModelMapping(mapping), dom, _) = *self; dom.mor_generators() .all(|e| mapping.apply_edge(e).map(|p| p.is_simple()).unwrap_or(true)) } /// Is the model morphism injective on objects? pub fn is_injective_objects(&self) -> bool { - let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, _) = *self; + let DblModelMorphism(DblModelMapping(mapping), dom, _) = *self; let mut seen_obs: HashSet<_> = HashSet::new(); for x in dom.ob_generators() { if let Some(f_x) = mapping.apply_vertex(x) { @@ -157,7 +154,7 @@ impl<'a> DiscreteDblModelMorphism<'a> { /// morphisms in the domain to simple paths in the codomain. If any of these /// assumptions are violated, the function will panic. pub fn is_free_simple_faithful(&self) -> bool { - let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, cod) = *self; + let DblModelMorphism(DblModelMapping(mapping), dom, cod) = *self; assert!(dom.is_free(), "Domain model should be free"); assert!(cod.is_free(), "Codomain model should be free"); diff --git a/packages/catlog/src/dbl/modal/diagram.rs b/packages/catlog/src/dbl/modal/diagram.rs new file mode 100644 index 000000000..3c43d0fa7 --- /dev/null +++ b/packages/catlog/src/dbl/modal/diagram.rs @@ -0,0 +1,190 @@ +//! Diagrams in models of a modal double theory. + +#[cfg(feature = "serde-wasm")] +use tsify::declare; + +use crate::dbl::{ + modal::ModalDblModelMapping, + model::{InvalidDblModel, ModalDblModel, MutDblModel}, + model_diagram::*, + model_morphism::{DblModelMorphism, InvalidDblModelMorphism}, + theory::Unital, +}; +use crate::one::{ + category::{Category, FgCategory}, + graph::GraphMapping, +}; +use crate::validate; +use crate::zero::{QualifiedName, column::Mapping}; + +use itertools::Either; +use nonempty::NonEmpty; + +/// A diagram is a model of a modal double theory. +pub type ModalDblModelDiagram = DblModelDiagram>; + +/// A failure to be valid in a diagram in a model of a discrete double theory. +#[cfg_attr(feature = "serde-wasm", declare)] +pub type InvalidModalDblModelDiagram = + InvalidDblModelDiagram>; + +impl Diagram for ModalDblModelDiagram { + type Model = ModalDblModel; + type Mapping = ModalDblModelMapping; + type InvalidDiagram = InvalidModalDblModelDiagram; + + /// Destructures the diagram + fn destructure(&self) -> (Self::Mapping, Self::Model) { + (self.0.clone(), self.1.clone()) + } + + /// Validates that the diagram is well-defined in the given model. + /// + /// Assumes that the model is valid. If it is not, this function may panic. + fn validate_in( + &self, + model: &ModalDblModel, + ) -> Result<(), NonEmpty> { + validate::wrap_errors(self.iter_invalid_in(model)) + } + + /// Iterates over failures of the diagram to be valid in the given model. + fn iter_invalid_in<'a>( + &'a self, + model: &'a ModalDblModel, + ) -> impl Iterator + 'a { + let mut dom_errs = self.1.iter_invalid().peekable(); + if dom_errs.peek().is_some() { + Either::Left(dom_errs.map(InvalidDblModelDiagram::Dom)) + } else { + let morphism = DblModelMorphism(&self.0, &self.1, model); + Either::Right(morphism.iter_invalid().map(InvalidDblModelDiagram::Map)) + } + } + + /// Infer missing data in the diagram from the model, where possible. + /// + /// Assumes that the model is valid. + fn infer_missing_from(&mut self, model: &ModalDblModel) { + let (mapping, domain) = self.into(); + domain.infer_missing(); + for e in domain.mor_generators() { + let Some(g) = mapping.0.edge_map().apply_to_ref(&e) else { + continue; + }; + if !model.has_mor(&g) { + continue; + } + + mapping.infer_missing(domain.clone().get_dom(&e), model.dom(&g)); + mapping.infer_missing(domain.clone().get_cod(&e), model.cod(&g)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dbl::model_diagram::DblModelDiagram; + use crate::dbl::model_morphism::MutDblModelMapping; + // use crate::stdlib::dec; + use crate::stdlib::th_multicategory; + use crate::tt::modelgen::Model; + use crate::validate; + use crate::zero::name; + use std::rc::Rc; + + // #[test] + // fn validate_modal_model_diagram() { + // let th = Rc::new(th_multicategory()); + // let dec = dec(th.clone()); + // // TODO does not like numbers + // let heat_eq = Model::from_text( + // &th.into(), + // "[ + // u : Object, + // dot_u : Object, + // k : Object, + // anon : Object, + // partial_t : Multihom[[u], dot_u], + // laplacian : Multihom[[u], anon], + // multiplication : Multihom[[k, anon], dot_u] + // ]", + // ); + // let heat_eq = heat_eq.unwrap().as_modal().unwrap(); + + // let mut f: ModalDblModelMapping = Default::default(); + // f.assign_ob(name("u"), name("Form0").into()); + // f.assign_ob(name("dot_u"), name("Form0").into()); + // f.assign_ob(name("k"), name("Form0").into()); + // f.assign_ob(name("anon"), name("Form0").into()); + // f.assign_mor(name("laplacian"), name("laplacian").into()); + // f.assign_mor(name("partial_t"), name("partial_t0").into()); + // f.assign_mor(name("multiplication"), name("multiplication").into()); + + // let diagram = DblModelDiagram(f, heat_eq); + // assert!(diagram.validate_in(&dec).is_ok()); + // } + + // #[test] + // fn validate_bad_modal_model_diagram() { + // let th = Rc::new(th_multicategory()); + // let dec = dec(th.clone()); + // // TODO does not like numbers + // let heat_eq = Model::from_text( + // &th.into(), + // "[ + // u : Object, + // dot_u : Object, + // k : Object, + // anon : Object, + // partial_t : Multihom[[u], dot_u], + // laplacian : Multihom[[u], anon], + // multiplication : Multihom[[k, anon], dot_u] + // ]", + // ); + // let heat_eq = heat_eq.unwrap().as_modal().unwrap(); + + // let mut f: ModalDblModelMapping = Default::default(); + // f.assign_ob(name("u"), name("Form0").into()); + // f.assign_ob(name("dot_u"), name("Form0").into()); + // f.assign_ob(name("k"), name("Form1").into()); // this is intentionally wrong. + // f.assign_ob(name("anon"), name("Form0").into()); + // f.assign_mor(name("laplacian"), name("laplacian").into()); + // f.assign_mor(name("partial_t"), name("partial_t0").into()); + // f.assign_mor(name("multiplication"), name("multiplication").into()); + + // let diagram = DblModelDiagram(f, heat_eq); + // let err = validate::wrap_errors( + // vec![InvalidDblModelDiagram::Map(InvalidDblModelMorphism::Dom(name( + // "multiplication", + // )))] + // .into_iter(), + // ); + // assert_eq!(diagram.validate_in(&dec), err); + // } + + // #[test] + // fn infer_modal_model_diagram() { + // let th = Rc::new(th_multicategory()); + // let domain = Model::from_text( + // &th.clone().into(), + // "[ + // u : Object, + // dot_u : Object, + // partial_t : Multihom[[u], dot_u] + // ]", + // ) + // .unwrap() + // .as_modal() + // .unwrap(); + + // let mut f: ModalDblModelMapping = Default::default(); + // f.assign_mor(name("partial_t"), name("partial_t0").into()); + // let mut diagram = DblModelDiagram(f, domain.clone()); + + // let dec = dec(th.clone()); + // diagram.infer_missing_from(&dec); + // assert!(diagram.validate_in(&dec).is_ok()); + // } +} diff --git a/packages/catlog/src/dbl/modal/mod.rs b/packages/catlog/src/dbl/modal/mod.rs index fe527ba76..dd20e65ce 100644 --- a/packages/catlog/src/dbl/modal/mod.rs +++ b/packages/catlog/src/dbl/modal/mod.rs @@ -1,7 +1,11 @@ //! Doctrine of modal double theories. +pub mod diagram; pub mod model; +pub mod morphism; pub mod theory; +pub use diagram::*; pub use model::*; +pub use morphism::*; pub use theory::*; diff --git a/packages/catlog/src/dbl/modal/model.rs b/packages/catlog/src/dbl/modal/model.rs index 11f3c9363..2a3f99c18 100644 --- a/packages/catlog/src/dbl/modal/model.rs +++ b/packages/catlog/src/dbl/modal/model.rs @@ -1,17 +1,21 @@ //! Models of modal double theories. -use std::collections::HashMap; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::rc::Rc; use std::sync::LazyLock; +use std::{collections::HashMap, fmt::Display}; use derive_more::From; use itertools::Itertools; use ref_cast::RefCast; use super::theory::*; -use crate::dbl::theory::DblTheoryKind; -use crate::dbl::{graph::VDblGraph, model::*, theory::DblTheory}; +use crate::dbl::{ + category::VDblCategory, + graph::VDblGraph, + model::*, + theory::{DblTheory, DblTheoryKind}, +}; use crate::tt::util::pretty::*; use crate::validate::{self, Validate}; use crate::{one::computad::*, one::*, zero::*}; @@ -30,6 +34,12 @@ pub enum ModalOb { List(List, Vec), } +impl Display for ModalOb { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:#?}", self) + } +} + /// Morphism is a model of a modal double theory. #[derive(Clone, Debug, PartialEq, Eq, From)] pub enum ModalMor { @@ -50,6 +60,16 @@ pub enum ModalMor { List(MorListData, Vec), } +impl ModalMor { + /// Render a [`QualifiedPath`] for snapshot output. + pub fn format_path(&self) -> String { + match &self { + ModalMor::Generator(name) => format!("{name}"), + _ => todo!(), + } + } +} + /// Extra data associated with a list of morphisms in a [list modality](List). #[derive(Clone, Debug, PartialEq, Eq)] pub enum MorListData { @@ -73,7 +93,7 @@ impl MorListData { } /// A model of a modal double theory. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct ModalDblModel { theory: Rc>, ob_generators: HashFinSet, @@ -100,6 +120,35 @@ impl ModalDblModel { fn computad(&self) -> Computad<'_, ModalOb, ModalDblModelObs, QualifiedName> { Computad::new(ModalDblModelObs::ref_cast(self), &self.mor_generators) } + + /// Infers missing data in the model, where possible. + /// + /// Objects used in the domain or codomain of morphisms, but not contained as + /// objects of the model, are added and their types are inferred. It is not + /// always possible to do this consistently, so it is important to `validate` + /// the model even after calling this method. + pub fn infer_missing(&mut self) { + let edges: Vec<_> = self.mor_generators().collect(); + for e in edges { + if let Some(x) = self.get_dom(&e).filter(|x| !self.has_ob(x)) { + let ob_type = self.theory.src(&self.mor_generator_type(&e)); + if let Some(id) = x.clone().generator() { + self.add_ob(id.clone(), ob_type) + }; + } + if let Some(x) = self.get_cod(&e).filter(|x| !self.has_ob(x)) { + let ob_type = self.theory.tgt(&self.mor_generator_type(&e)); + if let Some(id) = x.clone().generator() { + self.add_ob(id.clone(), ob_type) + }; + } + } + } + + /// Render a [`QualifiedPath`] for snapshot output. + pub fn format_path(&self, p: &ModalMorType) -> String { + format!("{:#?}", p) + } } #[derive(RefCast)] @@ -410,6 +459,13 @@ impl ModalDblModel { _ => false, } } + + // TODO + /// Iterates over failures of model to be well defined. + pub fn iter_invalid(&self) -> impl Iterator + '_ { + // type Invalid = InvalidDblModel; + vec![].into_iter() + } } impl ModalObOp { @@ -723,6 +779,9 @@ mod tests { ); model.add_mor(name("nullary"), ModalOb::List(List::Plain, vec![]), x.clone(), mor_type); assert!(model.validate().is_ok()); + + println!("{model}"); + dbg!(&model.mor_types); } #[test] diff --git a/packages/catlog/src/dbl/modal/morphism.rs b/packages/catlog/src/dbl/modal/morphism.rs new file mode 100644 index 000000000..efa4cf7de --- /dev/null +++ b/packages/catlog/src/dbl/modal/morphism.rs @@ -0,0 +1,172 @@ +//! Morphism between models of a modal double theory. + +use crate::dbl::modal::{ModalDblModel, ModalMor, ModalOb}; +use crate::dbl::model::MutDblModel; +use crate::dbl::model_morphism::{ + DblModelMapping, DblModelMorphism, InvalidDblModelMorphism, MutDblModelMapping, +}; +use crate::dbl::theory::Unital; +use crate::one::{ + FpFunctorData, + category::{Category, FgCategory}, + graph::GraphMapping, +}; +use crate::validate::{self, Validate}; +use crate::zero::{HashColumn, Mapping, MutMapping, QualifiedName}; + +use nonempty::NonEmpty; + +// TODO FpFunctorData on ModalDblModalMapping...? +// define a new struct `ModalDblModelMapping` which carries +type ModalDblModelMappingData = + FpFunctorData, HashColumn>; + +/// A mapping between models of a modal double theory. +pub type ModalDblModelMapping = DblModelMapping; + +impl MutDblModelMapping for ModalDblModelMapping { + type ObGen = ModalOb; + type MorGen = ModalMor; + /// Constructs a new model mapping from a pair of hash maps. + fn new( + ob_pairs: impl IntoIterator, + mor_pairs: impl IntoIterator, + ) -> Self { + Self(FpFunctorData::new( + ob_pairs.into_iter().collect(), + mor_pairs.into_iter().collect(), + )) + } + + /// Assigns an object generator, returning the previous assignment. + fn assign_ob(&mut self, x: QualifiedName, y: ModalOb) -> Option { + self.0.ob_generator_map.set(x, y) + } + + /// Assigns a morphism generator, returning the previous assignment. + fn assign_mor(&mut self, e: QualifiedName, n: ModalMor) -> Option { + self.0.mor_generator_map.set(e, n) + } + + /// Unassigns an object generator, returning the previous assignment. + fn unassign_ob(&mut self, x: &QualifiedName) -> Option { + self.0.ob_generator_map.unset(x) + } + + /// Unassigns a morphism generator, returning the previous assignment. + fn unassign_mor(&mut self, e: &QualifiedName) -> Option { + self.0.mor_generator_map.unset(e) + } + + // Finder of morphisms between two models of a modal double theory. + // fn morphisms<'a>( + // dom: &'a ModalDblModel, + // cod: &'a ModalDblModel, + // ) -> QualifiedName { + // todo!() + // } +} + +impl ModalDblModelMapping { + /// This checks if an object in the domain also exists in the model. + pub fn infer_missing(&mut self, domain_ob: Option<&ModalOb>, model_ob: ModalOb) { + if let Some(ob) = domain_ob { + let names: Vec = match ob { + ModalOb::Generator(name) => vec![name.clone()], + ModalOb::App(_, name) => vec![name.clone()], + ModalOb::List(_, args) => { + args.iter().filter_map(|ob| ob.clone().generator()).collect() + } + }; + + for name in names { + if !self.0.is_vertex_assigned(&name) { + match model_ob { + ref ob @ ModalOb::Generator(_) => self.assign_ob(name, ob.clone()), + ref ob @ ModalOb::App(_, _) => self.assign_ob(name, ob.clone()), + ModalOb::List(_, ref args) => match args.as_slice() { + [only] => self.assign_ob(name, only.clone()), + _ => todo!("What happens when we receive more than one arg?"), + }, + }; + }; + } + }; + } + + /// This applies a mapping onto a given object in the domain, returning the corresponding + /// object in the codomain. + fn apply_ob(&self, ob: ModalOb) -> Result { + match ob { + ModalOb::Generator(name) => { + self.0.apply_vertex(name.clone()).ok_or("Vertex {name} not found".to_string()) + } + ModalOb::App(_, name) => { + self.0.apply_vertex(name.clone()).ok_or("Vertex {name} not found".to_string()) + } + ModalOb::List(list, args) => args + .into_iter() + .map(|name| self.apply_ob(name.clone())) + .collect::, String>>() + .map(|args| ModalOb::List(list, args)), + } + } +} + +/// A morphism between models of a modal double theory. +// TODO kinds are fixed +pub type ModalDblModelMorphism<'a> = + DblModelMorphism<'a, ModalDblModelMapping, ModalDblModel, ModalDblModel>; + +impl<'a> ModalDblModelMorphism<'a> { + /// Iterates over failures of the mapping to be a model morphism. + pub fn iter_invalid( + &self, + ) -> impl Iterator> + 'a + use<'a> + { + // DiscreteDblModelMapping is destructured at this step, but I've decided not to + // destructure further out of convenience. + let DblModelMorphism(mapping, dom, cod) = *self; + + let ob_errors = dom.ob_generators().filter_map(|v| { + if mapping.0.vertex_map().apply_to_ref(&v).is_some_and(|w| cod.has_ob(&w)) { + None + } else { + Some(InvalidDblModelMorphism::::Ob(v)) + } + }); + + let mor_errors = dom.mor_generators().filter_map(|e| { + // Check if the morphism is correct. + let f = match mapping.0.edge_map().apply_to_ref(&e) { + Some(ModalMor::Generator(f)) if cod.has_mor(&ModalMor::Generator(f.clone())) => f, + Some(ModalMor::Generator(f)) => return Some(InvalidDblModelMorphism::Mor(f)), + _ => return None, + }; + + let dom_check = dom.get_dom(&e).zip(cod.get_dom(&f)).and_then(|(left, right)| { + (mapping.apply_ob(left.clone()) != Ok(right.clone())) + .then_some(InvalidDblModelMorphism::Dom(e.clone())) + }); + + let cod_check = dom.get_cod(&e).zip(cod.get_cod(&f)).and_then(|(left, right)| { + (mapping.apply_ob(left.clone()) != Ok(right.clone())) + .then_some(InvalidDblModelMorphism::Cod(e)) + }); + + // we're short-circuiting errors here. i'd like to collect them into one error message + // in the future + dom_check.or(cod_check) + }); + + ob_errors.chain(mor_errors) + } +} + +impl Validate for ModalDblModelMorphism<'_> { + type ValidationError = InvalidDblModelMorphism; + + fn validate(&self) -> Result<(), NonEmpty> { + validate::wrap_errors(self.iter_invalid()) + } +} diff --git a/packages/catlog/src/dbl/modal/theory.rs b/packages/catlog/src/dbl/modal/theory.rs index 57551ca69..1161b02ce 100644 --- a/packages/catlog/src/dbl/modal/theory.rs +++ b/packages/catlog/src/dbl/modal/theory.rs @@ -18,7 +18,7 @@ //! 2-category or monoidal category. Instead, the mode theory is implicit and baked //! in at the type level. -use std::fmt; +use std::fmt::{self, Display}; use std::hash::Hash; use std::iter::repeat_n; use std::marker::PhantomData; @@ -167,6 +167,12 @@ impl ModeApp { /// These are (object or morphism) types that cannot be built out of others. pub type ModalType = ModeApp; +impl Display for ModeApp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.arg) + } +} + /// A basic operation in a modal double theory. /// /// These are (object or morphism) operations that cannot be built out of others diff --git a/packages/catlog/src/dbl/model_diagram.rs b/packages/catlog/src/dbl/model_diagram.rs index fd3d5af48..73eabc448 100644 --- a/packages/catlog/src/dbl/model_diagram.rs +++ b/packages/catlog/src/dbl/model_diagram.rs @@ -8,20 +8,54 @@ //! fibered perspective, generalizing how a diagram in a category can be used to //! represent a copresheaf over that category. -use derive_more::Into; +use derive_more::{From, Into}; +use nonempty::NonEmpty; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "serde-wasm")] use tsify::Tsify; +use crate::dbl::discrete::DiscreteDblModelMapping; +use crate::dbl::modal::ModalDblModelMapping; +use crate::dbl::model::{DiscreteDblModel, FpDblModel, ModalDblModel, MutDblModel}; +use crate::dbl::theory::Unital; +use crate::one::FgCategory; + pub use super::discrete::model_diagram::*; +pub use super::modal::diagram::*; + +/// A diagram in a model of a double theory. +/// +/// This struct owns its data, namely, the domain of the diagram (a model) and the +/// model mapping itself. +pub trait Diagram { + type Model: FgCategory + FpDblModel + MutDblModel; + type Mapping; + type InvalidDiagram; + + fn destructure(&self) -> (Self::Mapping, Self::Model); + + fn validate_in(&self, model: &Self::Model) -> Result<(), NonEmpty>; + + fn iter_invalid_in<'a>( + &'a self, + model: &'a Self::Model, + ) -> impl Iterator + 'a; + + fn infer_missing_from(&mut self, model: &Self::Model); +} + +pub enum DblModelDiagramType { + Discrete(DblModelDiagram), + ModalUnital(DblModelDiagram>), +} /// A diagram in a model of a double theory. /// /// This struct owns its data, namely, the domain of the diagram (a model) and the /// model mapping itself. -#[derive(Clone, Into)] +#[derive(Clone, Into, From)] #[into(owned, ref, ref_mut)] pub struct DblModelDiagram(pub Map, pub Dom); diff --git a/packages/catlog/src/dbl/model_morphism.rs b/packages/catlog/src/dbl/model_morphism.rs index 3102bf82f..f428cc587 100644 --- a/packages/catlog/src/dbl/model_morphism.rs +++ b/packages/catlog/src/dbl/model_morphism.rs @@ -26,7 +26,59 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "serde-wasm")] use tsify::Tsify; +use crate::zero::QualifiedName; + pub use super::discrete::model_morphism::*; +pub use super::modal::morphism::*; + +/// Mapping +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct DblModelMapping(pub MappingData); + +/// Mapping +pub trait MutDblModelMapping { + /// Object generators for the DblModelMapping + type ObGen; + /// Morphism generators for the DblModelMapping + type MorGen; + + /// Constructs a model mapping from a pair of hash maps. + fn new( + ob_pairs: impl IntoIterator, + mor_pairs: impl IntoIterator, + ) -> Self; + + /// Assigns an object generator, returning the previous assignment. + fn assign_ob(&mut self, x: QualifiedName, y: Self::ObGen) -> Option; + + /// Assigns a morphism generator, returning the previous assignment. + fn assign_mor(&mut self, e: QualifiedName, n: Self::MorGen) -> Option; + + /// Unassigns an object generator, returning the previous assignment. + fn unassign_ob(&mut self, x: &QualifiedName) -> Option; + + /// Unassigns a morphism generator, returning the previous assignment. + fn unassign_mor(&mut self, e: &QualifiedName) -> Option; + + // /// Interprets the data as a functor into the given model. + // fn functor_into<'a>( + // &'a self, + // cod: &'a Self::DblModel, + // ) -> FpFunctor<'a, Self::DblModelMappingData, QualifiedFpCategory>; + + // /// Finder of morphisms between two models of a discrete double theory. + // fn morphisms<'a>( + // dom: &'a Self::DblModel, + // cod: &'a Self::DblModel, + // ) -> Self::DblModelMorphismFinder; +} + +/// A functor between models of a double theory. +/// +/// This struct borrows its data to perform validation. The domain and codomain are +/// assumed to be valid models of double theories. If that is in question, the +/// models should be validated *before* validating this object. +pub struct DblModelMorphism<'a, Map, Dom, Cod>(pub &'a Map, pub &'a Dom, pub &'a Cod); /// An invalid assignment in a morphism between models of a double theory. #[derive(Clone, Debug, Error, PartialEq, Eq)] diff --git a/packages/catlog/src/one/graph_algorithms.rs b/packages/catlog/src/one/graph_algorithms.rs index 3ef0d58ce..576e77570 100644 --- a/packages/catlog/src/one/graph_algorithms.rs +++ b/packages/catlog/src/one/graph_algorithms.rs @@ -1,6 +1,8 @@ //! Algorithms on graphs. use derivative::Derivative; +use derive_more::Constructor; +use indexmap::IndexMap; use std::collections::{HashMap, HashSet, VecDeque}; use std::hash::Hash; @@ -301,14 +303,44 @@ where } } +/// Contains both the topologically-sorted stack of vertices and feedback vertices. +#[derive(Debug, Clone, Constructor)] +pub struct ToposortData { + /// Stores an array of topologically-sorted vertices. + pub stack: Vec, + + /// Stores the feedback vertices with their outneighbors. + pub cycles: IndexMap>, +} + +type ToposortResult = Result, V>; + +/// Implementation of topological sort which returns an error when it encounters a cycle. +pub fn toposort_strict(graph: &G) -> Result, G::V> +where + G: FinGraph, + G::V: Hash + std::fmt::Debug, +{ + toposort_impl(graph, true).map(|t| t.stack) +} + +/// Implementation of topological sort which does not return an error when it encounters cycle. +pub fn toposort_lenient(graph: &G) -> ToposortData +where + G: FinGraph, + G::V: Hash + std::fmt::Debug, +{ + toposort_impl(graph, false).expect("toposort in lenient mode should return a valid result") +} + /// Computes a topological sorting for a given graph. /// /// This toposort algorithm was adapted from the crate `petgraph`, found /// [here](https://github.com/petgraph/petgraph/blob/4d807c19304c02c9dd687c68577f75aefcb98491/src/algo/mod.rs#L204). -pub fn toposort<'a, G>(graph: &'a G) -> Result, String> +fn toposort_impl(graph: &G, is_strict: bool) -> ToposortResult where G: FinGraph, - G::V: Hash + std::fmt::Debug + 'a, + G::V: Hash + std::fmt::Debug, { let mut finished = HashSet::new(); let mut finish_stack = Vec::new(); @@ -334,17 +366,23 @@ where // simply test directly by comparing positions of vertices. let position: HashMap = finish_stack.iter().enumerate().map(|(i, v)| (v.clone(), i)).collect(); + let mut cycles = IndexMap::new(); for e in graph.edges() { let s = graph.src(&e); let t = graph.tgt(&e); // Note that we did a DFS starting at every vertex, so it's impossible // that they don't appear _somewhere_ in our map. if position[&s] >= position[&t] { - return Err(format!("Cycle detected involving node {:#?}", s)); + if is_strict { + return Err(s); + } else { + let outs = graph.out_neighbors(&s).collect(); + cycles.insert(s, outs); + } } } - Ok(finish_stack) + Ok(ToposortData::new(finish_stack, cycles)) } #[cfg(test)] @@ -393,24 +431,26 @@ mod tests { #[test] fn toposorting() { let g = SkelGraph::path(5); - assert_eq!(toposort(&g), Ok(vec![0, 1, 2, 3, 4])); + let result = toposort_strict(&g); + assert_eq!(result.unwrap(), vec![0, 1, 2, 3, 4]); let mut g = SkelGraph::path(3); g.add_vertices(1); g.add_edge(2, 3); g.add_edge(3, 0); - expect_test::expect!["Cycle detected involving node 3"] - .assert_eq(&toposort(&g).unwrap_err()); + let t = &toposort_strict(&g).unwrap_err(); + expect_test::expect!["3"].assert_eq(&format!("{t}")); let g = SkelGraph::triangle(); - assert_eq!(toposort(&g), Ok(vec![0, 1, 2])); + assert_eq!(toposort_strict(&g).unwrap(), vec![0, 1, 2]); let mut g = SkelGraph::path(4); g.add_vertices(2); g.add_edge(1, 4); g.add_edge(4, 3); g.add_edge(5, 2); - assert_eq!(toposort(&g), Ok(vec![5, 0, 1, 2, 4, 3])); + + assert_eq!(toposort_strict(&g).unwrap(), vec![5, 0, 1, 2, 4, 3]); let mut g: HashGraph<_, _> = Default::default(); g.add_vertices(vec![0, 1, 2, 3, 4, 5]); @@ -420,9 +460,10 @@ mod tests { g.add_edge("1-4", 1, 4); g.add_edge("4-3", 4, 3); g.add_edge("5-2", 5, 2); - let sort = toposort(&g).unwrap(); - let (i0, i1) = (sort.iter().position(|&x| x == 5), sort.iter().position(|&x| x == 2)); - assert!(i0.unwrap() < i1.unwrap()); + if let Ok(sort) = toposort_strict(&g) { + let (i0, i1) = (sort.iter().position(|&x| x == 5), sort.iter().position(|&x| x == 2)); + assert!(i0.unwrap() < i1.unwrap()); + } } #[test] diff --git a/packages/catlog/src/one/path.rs b/packages/catlog/src/one/path.rs index a1e214918..98c977adf 100644 --- a/packages/catlog/src/one/path.rs +++ b/packages/catlog/src/one/path.rs @@ -50,6 +50,19 @@ pub enum Path { /// A path whose vertices and edges are qualified names. pub type QualifiedPath = Path; +impl QualifiedPath { + /// Render a [`QualifiedPath`] for snapshot output. + pub fn format_path(&self) -> String { + match &self { + Path::Id(v) => format!("Hom({v})"), + Path::Seq(es) => { + let parts: Vec = es.iter().map(|e| format!("{e}")).collect(); + parts.join(".") + } + } + } +} + /// A path in a graph with skeletal vertex and edge sets. pub type SkelPath = Path; @@ -485,6 +498,11 @@ impl TryFrom> for ShortPath { } impl ShortPath { + /// + pub fn format_path(&self) -> String { + "TODO".into() + } + /// Is the path contained in the given graph? pub fn contained_in(&self, graph: &impl Graph) -> bool { match self { diff --git a/packages/catlog/src/stdlib/analyses/sql.rs b/packages/catlog/src/stdlib/analyses/sql.rs index 8bbc10760..587835670 100644 --- a/packages/catlog/src/stdlib/analyses/sql.rs +++ b/packages/catlog/src/stdlib/analyses/sql.rs @@ -1,14 +1,20 @@ //! Produces a valid SQL data manipulation script from a model in the theory of schemas. use crate::{ dbl::model::*, - one::{Path, graph::FinGraph, graph_algorithms::toposort}, - zero::{QualifiedLabel, QualifiedName, label, name}, + one::{ + Path, + graph::FinGraph, + graph_algorithms::{ToposortData, toposort_lenient}, + }, + zero::{QualifiedLabel, QualifiedName, name}, }; +use derive_more::Constructor; use indexmap::IndexMap; use itertools::Itertools; +use nonempty::nonempty; use sea_query::SchemaBuilder; use sea_query::{ - ColumnDef, ForeignKey, ForeignKeyCreateStatement, Iden, MysqlQueryBuilder, + Alias, ColumnDef, ForeignKey, ForeignKeyCreateStatement, Iden, MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder, Table, TableCreateStatement, prepare::Write, }; use sqlformat::{Dialect, format}; @@ -32,35 +38,188 @@ impl Iden for &QualifiedLabel { } } +/// Enum for specifying the behavior of a column. For example, an Ordinary column is simply +/// a foreign key constraint. +#[derive(Debug, Clone, PartialEq)] +pub enum ColumnType { + /// A foreign key constraint. The target is an entity. + Ordinary { + /// The name of the morphism. + mor: QualifiedName, + /// The name of the target entity. + tgt: QualifiedName, + }, + /// A deferrable key constraint. The target is an entity. + Deferrable { + /// The name of the morphism. + mor: QualifiedName, + /// The name of the target entity. + tgt: QualifiedName, + }, + /// An attribute column. The target is an attribute type. + Attribute { + /// The name of the morphism. + mor: QualifiedName, + /// The name of the target attribute. + tgt: QualifiedName, + }, +} + +impl ColumnType { + fn build( + model: &DiscreteDblModel, + cycles: &IndexMap>, + src: &QualifiedName, + mor: QualifiedName, + ) -> Self { + let tgt = model.get_cod(&mor).unwrap(); + match model.mor_generator_type(&mor) { + t if t == Path::Seq(nonempty![name("Attr")]) => { + ColumnType::Attribute { mor, tgt: tgt.clone() } + } + _ => { + if cycles.contains_key(src) || cycles.contains_key(&tgt.clone()) { + ColumnType::Deferrable { mor, tgt: tgt.clone() } + } else { + ColumnType::Ordinary { mor, tgt: tgt.clone() } + } + } + } + } + + fn mor(&self) -> &QualifiedName { + match self { + ColumnType::Ordinary { mor, tgt: _ } + | ColumnType::Deferrable { mor, tgt: _ } + | ColumnType::Attribute { mor, tgt: _ } => mor, + } + } + + fn tgt(&self) -> &QualifiedName { + match self { + ColumnType::Ordinary { mor: _, tgt } + | ColumnType::Deferrable { mor: _, tgt } + | ColumnType::Attribute { mor: _, tgt } => tgt, + } + } + + /// The function creates foreign key constraints for PostgresSQL. Here, deferrable key + /// constraints are special. + fn render_postgres_fk( + &self, + src: &QualifiedName, + ob_label: impl Fn(&QualifiedName) -> String, + mor_label: impl Fn(&QualifiedName) -> String, + ) -> String { + let fk = |src: String, mor: &String, tgt: &String| -> String { + format!( + r#"ALTER TABLE "{src}" + ADD CONSTRAINT fk_{mor}_{src}_{tgt} + FOREIGN KEY ({mor}) REFERENCES "{tgt}" (id)"# + ) + }; + match self { + ColumnType::Ordinary { mor, tgt } => { + fk(ob_label(src), &mor_label(mor), &ob_label(tgt)) + ";" + } + ColumnType::Deferrable { mor, tgt } => { + fk(ob_label(src), &mor_label(mor), &ob_label(tgt)) + + "\n" + + r#"DEFERRABLE INITIALLY DEFERRED;"# + } + // this is unreachable, since attributes cannot be foreign keys. + ColumnType::Attribute { mor: _, tgt: _ } => unreachable!(), + } + } +} + +/// Data containing foreign key constraints and their behavior, which are interpreted as +/// backend-specific attributes. +#[derive(Clone, Debug)] +pub struct ForeignKeyConstraints { + /// Foreign key constraints for every table. + fks: IndexMap>, +} + +impl ForeignKeyConstraints { + fn new(model: &DiscreteDblModel) -> Self { + let g = model.generating_graph(); + let toposort: ToposortData = toposort_lenient(g); + let cycles = toposort.cycles; + let fks = IndexMap::from_iter(toposort.stack.into_iter().rev().filter_map(|v| { + (name("Entity") == model.ob_generator_type(&v)).then_some(( + v.clone(), + g.out_edges(&v) + .map(|e| ColumnType::build(model, &cycles, &v, e)) + .collect::>(), + )) + })); + Self { fks } + } + + fn any_deferrable(&self) -> bool { + self.fks + .values() + .flatten() + .into_iter() + .any(|s| matches!(s, ColumnType::Deferrable { mor: _, tgt: _ })) + } +} + +/// Error thrown when the SQL Analysis fails. +#[derive(Clone, Debug, PartialEq)] +pub enum SQLAnalysisError { + /// Its possible that a SQL backend cannot support cyclic foreign key constraints. + CyclicForeignKeyError { + /// The SQL backend that fails. Of the supported SQL backends, MySQL is the only one which + /// does not support cyclic foreign key constraints. + backend: SQLBackend, + /// The tables which have failing foreign key constraints. + cycles: Vec<(QualifiedName, ColumnType)>, + }, +} + +impl std::fmt::Display for SQLAnalysisError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + match self { + SQLAnalysisError::CyclicForeignKeyError { backend, cycles } => write!( + f, + "Cycle detected at tables {:#?}. {backend} cannot support cyclic foreign keys.", + cycles + ), + } + } +} + /// Struct for building a valid SQL DDL. +#[derive(Constructor)] pub struct SQLAnalysis { backend: SQLBackend, } impl SQLAnalysis { - /// Constructs a new SQLAnalysis instance. - pub fn new(backend: SQLBackend) -> Self { - Self { backend } + /// Returns formatted output. + pub fn format(&self, output: &str) -> String { + format( + output, + &sqlformat::QueryParams::None, + &sqlformat::FormatOptions { + lines_between_queries: 2, + dialect: self.backend.clone().into(), + ..Default::default() + }, + ) } - /// Consumes itself and a discrete double model to produce a SQL string. - pub fn render( + /// Builds table statements into valid SQL DML. + fn build( &self, - model: &DiscreteDblModel, - ob_label: impl Fn(&QualifiedName) -> QualifiedLabel, - mor_label: impl Fn(&QualifiedName) -> QualifiedLabel, - ) -> Result { - let g = model.generating_graph(); - let t = toposort(g).map_err(|e| format!("Topological sort failed: {}", e))?; - let morphisms: IndexMap<&QualifiedName, Vec> = - IndexMap::from_iter(t.iter().rev().filter_map(|v| { - (name("Entity") == model.ob_generator_type(v)) - .then_some((v, g.out_edges(v).collect::>())) - })); - - let tables = self.make_tables(model, morphisms, ob_label, mor_label); - - let output: String = tables + tables: Vec, + constraints: ForeignKeyConstraints, + ob_label: impl Fn(&QualifiedName) -> String, + mor_label: impl Fn(&QualifiedName) -> String, + ) -> String { + let table_def: String = tables .iter() .map(|table| match self.backend { SQLBackend::MySQL => table.to_string(MysqlQueryBuilder), @@ -70,80 +229,132 @@ impl SQLAnalysis { .join(";\n") + ";"; - // TODO SQL analysis should interface with this - let formatted_output = format( - &output, - &sqlformat::QueryParams::None, - &sqlformat::FormatOptions { - lines_between_queries: 2, - dialect: self.backend.clone().into(), - ..Default::default() - }, - ); + // for PostgresSQL only + let deferrable_fks: String = constraints + .fks + .iter() + .flat_map(|(ob, mors)| { + mors.iter() + .filter(|fkb| matches!(fkb, ColumnType::Deferrable { mor: _, tgt: _ })) + .map(|fkb| fkb.render_postgres_fk(ob, &ob_label, &mor_label)) + .collect::>() + }) + .join("\n"); - let result = match self.backend { - SQLBackend::SQLite => ["PRAGMA foreign_keys = ON", &formatted_output].join(";\n\n"), - _ => formatted_output, - }; - Ok(result) + table_def + &deferrable_fks } - fn fk( + fn validate_toposort( &self, - src_name: QualifiedLabel, - tgt_name: QualifiedLabel, - mor_name: QualifiedLabel, - ) -> ForeignKeyCreateStatement { + constraints: ForeignKeyConstraints, + ) -> Result { + // TODO: punting fixing SQLite cycles for now + if (self.backend == SQLBackend::MySQL || self.backend == SQLBackend::SQLite) + && constraints.any_deferrable() + { + let cycles = constraints + .fks + .into_iter() + .flat_map(|(k, v)| v.into_iter().map(move |e| (k.clone(), e))) + .filter(|(_, e)| matches!(e, ColumnType::Deferrable { mor: _, tgt: _ })) + .collect::>(); + Err(SQLAnalysisError::CyclicForeignKeyError { backend: self.backend.clone(), cycles }) + } else { + Ok(constraints) + } + } + + fn toposort_morphisms( + &self, + model: &DiscreteDblModel, + ) -> Result { + // if a morphism is a key in toposort.cycles, then its source and targets are deferrable. + let constraints = ForeignKeyConstraints::new(model); + self.validate_toposort(constraints) + } + + /// Consumes itself and a discrete double model to produce a SQL string. + pub fn render( + &self, + model: &DiscreteDblModel, + ob_label: impl Fn(&QualifiedName) -> String, + mor_label: impl Fn(&QualifiedName) -> String, + ) -> Result { + let constraints = self.toposort_morphisms(model); + let tables = self.make_tables(model, constraints.clone()?, &ob_label, &mor_label); + let output: String = self.build(tables, constraints.clone()?, ob_label, mor_label); + let formatted_output = self.format(&output); + // pragmas + match self.backend { + SQLBackend::SQLite => Ok(["PRAGMA foreign_keys = ON", &formatted_output].join(";\n\n")), + _ => Ok(formatted_output), + } + } + + fn fk(&self, src: &str, tgt: &str, mor: &str) -> ForeignKeyCreateStatement { ForeignKey::create() - .name(format!("FK_{}_{}_{}", mor_name, src_name, tgt_name)) - .from(src_name.clone(), mor_name) - .to(tgt_name.clone(), "id") + .name(format!("FK_{}_{}_{}", mor, src, tgt)) + .from(Alias::new(src), Alias::new(mor)) + .to(Alias::new(tgt), "id") .to_owned() } fn make_tables( &self, model: &DiscreteDblModel, - morphisms: IndexMap<&QualifiedName, Vec>, - ob_label: impl Fn(&QualifiedName) -> QualifiedLabel, - mor_label: impl Fn(&QualifiedName) -> QualifiedLabel, + constraints: ForeignKeyConstraints, + ob_label: impl Fn(&QualifiedName) -> String, + mor_label: impl Fn(&QualifiedName) -> String, ) -> Vec { - morphisms + constraints + .fks .into_iter() .map(|(ob, mors)| { let mut tbl = Table::create(); // the targets for arrows let table_column_defs = mors.iter().fold( - tbl.table(ob_label(ob)).if_not_exists().col( + tbl.table(Alias::new(ob_label(&ob))).if_not_exists().col( ColumnDef::new("id").integer().not_null().auto_increment().primary_key(), ), |acc, mor| { - let mor_name = mor_label(mor); + let mor_tgt = mor.tgt(); + let ob_name = ob_label(mor_tgt); + let mor_name = mor_label(mor.mor()); // if the Id of the name is an entity, it is assumed to be a column // which references the primary key of another table. - if model.mor_generator_type(mor) == Path::Id(name("Entity")) { - acc.col(ColumnDef::new(mor_name.clone()).integer().not_null()) + if model.mor_generator_type(mor.mor()) == Path::Id(name("Entity")) { + acc.col( + ColumnDef::new(Alias::new(mor_name.as_str())).integer().not_null(), + ) } else { - let tgt = - model.get_cod(mor).map(&ob_label).unwrap_or_else(|| label("")); - let mut col = ColumnDef::new(mor_name); + let mut col = ColumnDef::new(Alias::new(mor_name.as_str())); col.not_null(); - add_column_type(&mut col, &tgt); + add_column_type(&mut col, ob_name.as_str()); acc.col(col) } }, ); mors.iter() - .filter(|mor| model.mor_generator_type(mor) == Path::Id(name("Entity"))) + .filter(|mor| { + (model.mor_generator_type(mor.mor()) == Path::Id(name("Entity"))) + && (if self.backend == SQLBackend::PostgresSQL { + matches!(mor, ColumnType::Ordinary { mor: _, tgt: _ }) + } else { + true + }) + }) .fold( // TABLE AND COLUMN DEFS table_column_defs, |acc, mor| { - let tgt = - model.get_cod(mor).map(&ob_label).unwrap_or_else(|| label("")); - acc.foreign_key(&mut self.fk(ob_label(ob), tgt, mor_label(mor))) + // if there is a cyclic pattern, we want to add deferrable... + acc.foreign_key(&mut self.fk( + ob_label(&ob).as_str(), + ob_label(mor.tgt()).as_str(), + mor_label(mor.mor()).as_str(), + )) }, ) .to_owned() @@ -155,7 +366,7 @@ impl SQLAnalysis { /// Variants of SQL backends. Each correspond to types which implement the /// `SchemaBuilder` trait that is used to render into the correct backend. The `SchemaBuilder` and /// the types implementing that trait are owned by `sea_query`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum SQLBackend { /// The MySQL backend. MySQL, @@ -210,8 +421,8 @@ impl fmt::Display for SQLBackend { } } -fn add_column_type(col: &mut ColumnDef, name: &QualifiedLabel) { - match format!("{}", name).as_str() { +fn add_column_type(col: &mut ColumnDef, label: &str) { + match label { "Int" => col.integer(), "TinyInt" => col.tiny_integer(), "Bool" => col.boolean(), @@ -219,7 +430,7 @@ fn add_column_type(col: &mut ColumnDef, name: &QualifiedLabel) { "Time" => col.timestamp(), "Date" => col.date(), "DateTime" => col.date_time(), - _ => col.custom(name.clone()), + _ => col.custom(Alias::new(label)), }; } @@ -234,17 +445,17 @@ mod tests { #[test] fn sql_schema() { let th = Rc::new(th_schema()); - let model = tt::modelgen::Model::from_text( - &th.into(), - "[ + let source = "[ Person : Entity, Dog : Entity, walks : (Hom Entity)[Person, Dog], Hair : AttrType, has : Attr[Person, Hair], - ]", - ); - let model = model.unwrap().as_discrete().unwrap(); + ]"; + let model = tt::modelgen::Model::from_text(&th.clone().into(), source) + .ok() + .and_then(|m| m.as_discrete()) + .unwrap(); let expected = expect![[ r#"CREATE TABLE IF NOT EXISTS `Dog` (`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY); @@ -265,4 +476,96 @@ CREATE TABLE IF NOT EXISTS `Person` ( .expect("SQL should render"); expected.assert_eq(&ddl); } + + #[test] + fn sql_postgres_cycles() { + let th = Rc::new(th_schema()); + let source = "[ + Refs : Entity, + Snapshots : Entity, + head : (Hom Entity)[Refs, Snapshots], + for_ref: (Hom Entity)[Snapshots, Refs], + Timestamp : AttrType, + created : Attr[Refs, Timestamp], + last_updated: Attr[Snapshots, Timestamp], + ]"; + let model = tt::modelgen::Model::from_text(&th.into(), source) + .ok() + .and_then(|m| m.as_discrete()) + .unwrap(); + + let expected = expect![[r#"CREATE TABLE IF NOT EXISTS "Snapshots" ( + "id" serial NOT NULL PRIMARY KEY, + "for_ref" integer NOT NULL, + "last_updated" Timestamp NOT NULL +); + +CREATE TABLE IF NOT EXISTS "Refs" ( + "id" serial NOT NULL PRIMARY KEY, + "head" integer NOT NULL, + "created" Timestamp NOT NULL +); + +ALTER TABLE + "Snapshots" +ADD + CONSTRAINT fk_for_ref_Snapshots_Refs FOREIGN KEY (for_ref) REFERENCES "Refs" (id) DEFERRABLE INITIALLY DEFERRED; + +ALTER TABLE + "Refs" +ADD + CONSTRAINT fk_head_Refs_Snapshots FOREIGN KEY (head) REFERENCES "Snapshots" (id) DEFERRABLE INITIALLY DEFERRED;"#]]; + let ddl = SQLAnalysis::new(SQLBackend::PostgresSQL) + .render( + &model, + |id| format!("{id}").as_str().into(), + |id| format!("{id}").as_str().into(), + ) + .expect("SQL should render"); + expected.assert_eq(&ddl); + } + + #[test] + fn sql_mysql_cycles() { + let th = Rc::new(th_schema()); + let source = "[ + Refs : Entity, + Snapshots : Entity, + head : (Hom Entity)[Refs, Snapshots], + for_ref: (Hom Entity)[Snapshots, Refs], + Timestamp : AttrType, + created : Attr[Refs, Timestamp], + last_updated: Attr[Snapshots, Timestamp], + ]"; + let model = tt::modelgen::Model::from_text(&th.into(), source) + .ok() + .and_then(|m| m.as_discrete()) + .unwrap(); + + let ddl = SQLAnalysis::new(SQLBackend::MySQL).render( + &model, + |id| format!("{id}").as_str().into(), + |id| format!("{id}").as_str().into(), + ); + let e = ddl.unwrap_err(); + assert_eq!( + e, + SQLAnalysisError::CyclicForeignKeyError { + backend: SQLBackend::MySQL, + cycles: vec![ + ( + name("Snapshots"), + ColumnType::Deferrable { mor: name("for_ref"), tgt: name("Refs") } + ), + ( + name("Refs"), + ColumnType::Deferrable { + mor: name("head"), + tgt: name("Snapshots") + } + ) + ] + } + ); + } } diff --git a/packages/catlog/src/tt/batch.rs b/packages/catlog/src/tt/batch.rs index 42be88292..31c12444a 100644 --- a/packages/catlog/src/tt/batch.rs +++ b/packages/catlog/src/tt/batch.rs @@ -6,13 +6,19 @@ use std::ops::DerefMut; use std::time::{Duration, Instant}; use std::{fs, io}; +use all_the_same::all_the_same; use fnotation::FNtnTop; use scopeguard::guard; use tattle::display::SourceInfo; use tattle::{Reporter, declare_error}; -use super::{text_elab::*, theory::std_theories, toplevel::*}; -use crate::zero::NameSegment; +use super::{modelgen::diagram_from_diag, text_elab::*, theory::std_theories, toplevel::*}; +use crate::dbl::discrete::InvalidDiscreteDblModelDiagram; +use crate::dbl::model::{FpDblModel, MutDblModel}; +use crate::dbl::model_diagram::{DblModelDiagramType, Diagram}; +use crate::one::category::FgCategory; +use crate::tt::modelgen::Model; +use crate::zero::{Mapping, NameSegment}; declare_error!(TOP_ERROR, "top", "an error at the top-level"); @@ -64,6 +70,66 @@ impl BatchOutput { } } + fn diagram_summary(&self, diagram: &DblModelDiagramType) { + if let BatchOutput::Snapshot(out) = self { + all_the_same!(match diagram { + DblModelDiagramType::[Discrete, ModalUnital](diagram) => { + let (mapping, domain) = diagram.destructure(); + let mut out = out.borrow_mut(); + let ob_gens: Vec<_> = domain.ob_generators().collect(); + let mor_gens: Vec<_> = domain.mor_generators().collect(); + if ob_gens.is_empty() && mor_gens.is_empty() { + writeln!(out, "#/ diagram has no domain generators").unwrap(); + return; + } + writeln!(out, "#/ diagram domain:").unwrap(); + for g in &ob_gens { + let ot = domain.ob_generator_type(g); + match mapping.0.ob_generator_map.apply_to_ref(g) { + Some(target) => writeln!(out, "#/ {g} : {ot} -> {target}").unwrap(), + None => writeln!(out, "#/ {g} : {ot}").unwrap(), + } + } + for g in &mor_gens { + let mt = &domain.mor_generator_type(g).format_path(); + let dom_str = + domain.get_dom(g).map(|o| format!("{o}")).unwrap_or_else(|| "?".into()); + let cod_str = + domain.get_cod(g).map(|o| format!("{o}")).unwrap_or_else(|| "?".into()); + let target_str = mapping + .0 + .mor_generator_map + .apply_to_ref(g) + .map(|p| format!(" -> {}", &p.format_path())) + .unwrap_or_default(); + writeln!(out, "#/ {g} : {mt}[{dom_str}, {cod_str}]{target_str}").unwrap(); + } + } + }) + } + } + + fn diagram_error(&self, msg: &str) { + if let BatchOutput::Snapshot(out) = self { + writeln!(out.borrow_mut(), "#/ diagram generation failed: {msg}").unwrap(); + } + } + + fn validation_result>(&self, errs: I) { + if let BatchOutput::Snapshot(out) = self { + let mut out = out.borrow_mut(); + let mut iter = errs.into_iter().peekable(); + if iter.peek().is_none() { + writeln!(out, "#/ diagram validates against codomain").unwrap(); + } else { + writeln!(out, "#/ diagram validation failed:").unwrap(); + for e in iter { + writeln!(out, "#/ {e:?}").unwrap(); + } + } + } + } + fn got_result(&self, result: &str) { match self { BatchOutput::Snapshot(out) => { @@ -179,8 +245,41 @@ pub fn elaborate(src: &str, path: &str, output: &BatchOutput) -> io::Result { + let is_diag = matches!(top_decl, TopDecl::Diag(_)); toplevel.declarations.insert(name_segment, top_decl); output.declared(name_segment); + if is_diag + && let Some(TopDecl::Diag(diag)) = + toplevel.declarations.get(&name_segment) + { + match diagram_from_diag(&toplevel, &diag.theory.definition, diag) { + Ok((model_diag, _, _)) => { + output.diagram_summary(&model_diag); + let (cod, _) = Model::from_ty( + &toplevel, + &diag.theory.definition, + &diag.model, + ); + match &model_diag { + DblModelDiagramType::Discrete(diagram) => { + if let Some(cod) = cod.as_discrete() { + output.validation_result( + diagram.iter_invalid_in(&cod), + ); + } + } + DblModelDiagramType::ModalUnital(diagram) => { + if let Some(cod) = cod.as_modal() { + output.validation_result( + diagram.iter_invalid_in(&cod), + ); + } + } + } + } + Err(msg) => output.diagram_error(&msg), + } + } } TopElabResult::Output(s) => { output.got_result(&s); diff --git a/packages/catlog/src/tt/context.rs b/packages/catlog/src/tt/context.rs index 0e313d478..8a60f19c7 100644 --- a/packages/catlog/src/tt/context.rs +++ b/packages/catlog/src/tt/context.rs @@ -4,6 +4,21 @@ use derive_more::Constructor; use crate::tt::{prelude::*, val::*}; +/// What kind of binding a context entry represents. +/// +/// Most bindings are ordinary `Term` bindings. `Diagram` bindings are pushed +/// by the `diagram` toplevel arm so that `@over .E` can recover the +/// enclosing diagram's name and codomain type without exposing the diagram +/// to ordinary term lookup (where it would masquerade as a term of the +/// codomain type). +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum VarKind { + /// An ordinary term-level binding. + Term, + /// A binding for a diagram declaration; not resolvable by [`Context::lookup`]. + Diagram, +} + /// Each variable in context is associated with a label and a type. /// /// Multiple variables with the same name can show up in context; in this case @@ -19,6 +34,8 @@ pub struct VarInContext { /// We allow the type to be null as a hack for the `self` variable before we /// know the type of the `self` variable. pub ty: Option, + /// What kind of binding this is. + pub kind: VarKind, } /// The variable context during elaboration. @@ -61,18 +78,38 @@ impl Context { self.scope.truncate(c.scope); } - /// Add a new variable to scope (note: does not add it to the environment). + /// Add a new term-kind variable to scope (note: does not add it to the environment). pub fn push_scope(&mut self, name: VarName, label: LabelSegment, ty: Option) { - self.scope.push(VarInContext::new(name, label, ty)) + self.scope.push(VarInContext::new(name, label, ty, VarKind::Term)) } - /// Lookup a variable by name. + /// Add a new diagram-kind variable to scope. + pub fn push_diagram(&mut self, name: VarName, label: LabelSegment, ty: TyV) { + self.scope.push(VarInContext::new(name, label, Some(ty), VarKind::Diagram)) + } + + /// Lookup a term-kind variable by name. + /// + /// Diagram-kind entries are skipped, so a `Var(I)` referring to an + /// enclosing `diagram I := ...` will not resolve here. pub fn lookup(&self, name: VarName) -> Option<(BwdIdx, LabelSegment, Option)> { self.scope .iter() .rev() .enumerate() - .find(|(_, v)| v.name == name) + .find(|(_, v)| v.kind == VarKind::Term && v.name == name) .map(|(i, v)| (i.into(), v.label, v.ty.clone())) } + + /// Find the most recent diagram-kind binding in scope, if any. + /// + /// Returns the diagram's name and codomain type. Used by the `@over .E` + /// type elaborator. + pub fn lookup_diagram(&self) -> Option<(VarName, TyV)> { + self.scope + .iter() + .rev() + .find(|v| v.kind == VarKind::Diagram) + .map(|v| (v.name, v.ty.clone().expect("diagram binding must have a type"))) + } } diff --git a/packages/catlog/src/tt/eval.rs b/packages/catlog/src/tt/eval.rs index bce90d26f..af27e23fc 100644 --- a/packages/catlog/src/tt/eval.rs +++ b/packages/catlog/src/tt/eval.rs @@ -49,7 +49,11 @@ impl<'a> Evaluator<'a> { /// to self.env. pub fn eval_ty(&self, ty: &TyS) -> TyV { match &**ty { - TyS_::TopVar(tv) => self.toplevel.declarations.get(tv).unwrap().clone().unwrap_ty().val, + TyS_::TopVar(tv) => match self.toplevel.declarations.get(tv).unwrap() { + TopDecl::Type(t) => t.val.clone(), + TopDecl::Diag(d) => d.body_val.clone(), + _ => panic!("top-level {tv} should be a type or diagram declaration"), + }, TyS_::Object(ot) => TyV::object(ot.clone()), TyS_::Morphism(pt, dom, cod) => { TyV::morphism(pt.clone(), self.eval_tm(dom), self.eval_tm(cod)) @@ -66,6 +70,7 @@ impl<'a> Evaluator<'a> { } TyS_::Unit => TyV::unit(), TyS_::Meta(mv) => TyV::meta(*mv), + TyS_::Over(path) => TyV::over(path.clone()), } } @@ -192,6 +197,7 @@ impl<'a> Evaluator<'a> { } TyV_::Unit => TyS::unit(), TyV_::Meta(mv) => TyS::meta(*mv), + TyV_::Over(path) => TyS::over(path.clone()), } } @@ -255,6 +261,7 @@ impl<'a> Evaluator<'a> { TyV_::Id(_, _, _) => Ok(()), TyV_::Unit => Ok(()), TyV_::Meta(_) => Ok(()), + TyV_::Over(_) => Ok(()), } } @@ -299,6 +306,13 @@ impl<'a> Evaluator<'a> { (TyV_::Sing(ty1, _), _) => self.convertible_ty(ty1, ty2), (_, TyV_::Sing(ty2, _)) => self.convertible_ty(ty1, ty2), (TyV_::Unit, TyV_::Unit) => Ok(()), + (TyV_::Over(p1), TyV_::Over(p2)) => { + if p1 == p2 { + Ok(()) + } else { + Err(t("over-types refer to different paths in the codomain")) + } + } _ => Err(t("tried to convert between types of different type constructors")), } } @@ -321,6 +335,7 @@ impl<'a> Evaluator<'a> { TyV_::Id(_, _, _) => TmV::tt(), // Extensional equality at a 100% discount! TyV_::Unit => TmV::tt(), TyV_::Meta(_) => TmV::neu(n.clone(), ty.clone()), + TyV_::Over(_) => TmV::neu(n.clone(), ty.clone()), } } @@ -423,6 +438,8 @@ impl<'a> Evaluator<'a> { } } + // TODO: refactor to use [`Evaluator::path_ty`] for the descent and + // keep only the subtype check here. fn can_specialize( &self, ty: &TyV, @@ -455,6 +472,34 @@ impl<'a> Evaluator<'a> { } } + /// Walk `path` from the value `val` of record type `ty`, returning + /// the type of the field at the end of the path. + /// + /// An empty path returns `ty` unchanged. Each segment requires the + /// current type to be a record containing the named field. + pub fn path_ty( + &self, + ty: &TyV, + val: &TmV, + path: &[(FieldName, LabelSegment)], + ) -> Result { + let mut ty = ty.clone(); + let mut val = val.clone(); + for &(name, label) in path { + let TyV_::Record(r) = &*ty.clone() else { + return Err(format!("expected a record type at .{label}")); + }; + if !r.fields.has(name) { + return Err(format!("no such field .{label}")); + } + let next_ty = self.field_ty(&ty, &val, name); + let next_val = self.proj(&val, name, label); + ty = next_ty; + val = next_val; + } + Ok(ty) + } + /// Try to specialize the record `r` with the subtype `ty` at `path`. /// /// Precondition: `path` is non-empty. diff --git a/packages/catlog/src/tt/modelgen.rs b/packages/catlog/src/tt/modelgen.rs index 3b5bcb3b0..939465b4c 100644 --- a/packages/catlog/src/tt/modelgen.rs +++ b/packages/catlog/src/tt/modelgen.rs @@ -5,16 +5,23 @@ use derive_more::{From, TryInto}; use tattle::display::SourceInfo; use super::{eval::*, prelude::*, text_elab, theory::*, toplevel::*, val::*}; -use crate::dbl::{ - discrete, discrete_tabulator, modal, - model::{DblModel, DblModelPrinter, MutDblModel}, - theory::{DblTheory, DblTheoryKind, NonUnital, Unital}, -}; use crate::one::{ - Category, + Category, FgCategory, QualifiedPath, path::{Path, PathEq}, }; use crate::zero::{Namespace, QualifiedName}; +use crate::{ + dbl::{ + discrete::{self, DiscreteDblModelMapping}, + discrete_tabulator, + modal::{self, ModalDblModel, ModalDblModelMapping, ModalMorType, ModalOb, ModalObType}, + model::{DblModel, DblModelPrinter, DiscreteDblModel, MutDblModel}, + model_diagram::{DblModelDiagram, DblModelDiagramType}, + model_morphism::MutDblModelMapping, + theory::{DblTheory, DblTheoryKind, NonUnital, Unital}, + }, + tt::stx::TmS, +}; /// A model generated by DoubleTT. /// @@ -180,9 +187,11 @@ impl Model { Model::DiscreteTab(_) => { // Discrete tabulator models currently do not support equations, so we ignore them. } - Model::ModalUnital(_) | Model::ModalNonUnital(_) => { + Model::ModalUnital(_) => { + // model.add_equation(PathEq::new(lhs.try_into().unwrap(), rhs.try_into().unwrap())); // Modal models currently do not support equations, so we ignore them. } + Model::ModalNonUnital(_) => {} } } @@ -398,6 +407,545 @@ impl<'a> ModelGenerator<'a> { } TyV_::Unit => None, TyV_::Meta(_) => None, + TyV_::Over(_) => None, + } + } +} + +/// Generates a [`discrete::DiscreteDblModelDiagram`] from an elaborated [`Diag`]. +/// +/// Restricted to discrete double theories for the moment; other theory +/// kinds will need their own diagram types in catlog before this can be +/// promoted to an enum mirroring [`Model`]. +pub fn diagram_from_diag( + toplevel: &Toplevel, + th: &TheoryDef, + diag: &Diag, +) -> Result<(DblModelDiagramType, Namespace, Vec<(TmS, TmS)>), String> { + match th { + TheoryDef::Discrete(theory) => { + let mut generator = DiagramGenerator { + eval: Evaluator::empty(toplevel), + codomain_ty: diag.model.clone(), + domain: discrete::DiscreteDblModel::new(theory.clone()), + mapping: discrete::DiscreteDblModelMapping::default(), + equations: vec![], + }; + let namespace = generator.generate(&diag.body_val)?; + let DiagramGenerator { domain, mapping, equations, .. } = generator; + Ok(( + DblModelDiagramType::Discrete(DblModelDiagram(mapping, domain)), + namespace, + equations, + )) + } + TheoryDef::ModalUnital(theory) => { + let mut generator = DiagramGenerator { + eval: Evaluator::empty(toplevel), + codomain_ty: diag.model.clone(), + domain: modal::ModalDblModel::new(theory.clone()), + mapping: modal::ModalDblModelMapping::default(), + equations: vec![], + }; + let namespace = generator.generate(&diag.body_val)?; + let DiagramGenerator { domain, mapping, equations, .. } = generator; + Ok(( + DblModelDiagramType::ModalUnital(DblModelDiagram(mapping, domain)), + namespace, + equations, + )) + } + _ => Err("!".into()), + } +} + +struct DiagramGenerator<'a, M: DblModel + MutDblModel + FgCategory, Mapping: MutDblModelMapping> { + eval: Evaluator<'a>, + codomain_ty: TyV, + domain: M, + mapping: Mapping, + // I've added this + equations: Vec<(TmS, TmS)>, +} + +impl DiagramGenerator<'_, DiscreteDblModel, DiscreteDblModelMapping> { + fn generate(&mut self, body_ty: &TyV) -> Result { + let (self_n, eval_with_self) = self.eval.bind_self(body_ty.clone()); + self.eval = eval_with_self; + let self_val = self.eval.eta_neu(&self_n, body_ty); + Ok(self + .extract(vec![], &self_val, body_ty)? + .unwrap_or_else(Namespace::new_for_uuid)) + } + + fn extract( + &mut self, + prefix: Vec, + val: &TmV, + ty: &TyV, + ) -> Result, String> { + match &**ty { + TyV_::Record(r) => { + let mut namespace = Namespace::new_for_uuid(); + for (name, (label, _)) in r.fields.iter() { + let mut child_prefix = prefix.clone(); + child_prefix.push(*name); + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + let field_tm = self.eval.proj(val, *name, *label); + let field_ty = self.eval.field_ty(ty, val, *name); + if let Some(inner) = self.extract(child_prefix, &field_tm, &field_ty)? { + namespace.add_inner(*name, inner); + } + } + Ok(Some(namespace)) + } + TyV_::Over(path) => { + // Resolve the path against the codomain to find the + // catlog object type for this generator. + let (cod_self_n, cod_eval) = self.eval.bind_self(self.codomain_ty.clone()); + let cod_val = cod_eval.eta_neu(&cod_self_n, &self.codomain_ty); + let resolved = cod_eval.path_ty(&self.codomain_ty, &cod_val, path)?; + let TyV_::Object(ot) = &*resolved else { + return Err( + "@over path does not refer to an object generator in the codomain".into() + ); + }; + let ot: QualifiedName = ot.clone().try_into().map_err(|_| { + "@over codomain object type is not valid for a discrete theory".to_string() + })?; + let qname: QualifiedName = prefix.into(); + self.domain.add_ob(qname.clone(), ot); + let target: QualifiedName = + path.iter().map(|(seg, _)| *seg).collect::>().into(); + self.mapping.assign_ob(qname, target); + Ok(None) + } + TyV_::Morphism(mt, dom, cod) => { + // Augmentation produces lift morphisms whose dom and cod + // are projections from the body's self, resolving to the + // qualified names of generators (declared or specialized). + dbg!(&mt, &dom); + let dom_name = neutral_qualified_name(dom).ok_or_else(|| { + "morphism domain does not resolve to a generator name".to_string() + })?; + let cod_name = neutral_qualified_name(cod).ok_or_else(|| { + "morphism codomain does not resolve to a generator name".to_string() + })?; + let mt_inner: QualifiedPath = mt + .clone() + .try_into() + .map_err(|_| "morphism type is not valid for a discrete theory".to_string())?; + let qname: QualifiedName = prefix.clone().into(); + self.domain.add_mor( + qname.clone(), + dom_name.into(), + cod_name.into(), + mt_inner.into(), + ); + // Mapping target: extract the codomain morphism's name + // from the synthetic lift name `~f(e)`. For other shapes + // (e.g., a future user-declared morphism in a body), fall + // back to the field name itself. + if let Some(last) = prefix.last() + && let Some(cod_mor) = parse_lift_morphism_name(last) + { + let target = Path::single(QualifiedName::single(cod_mor)); + self.mapping.assign_mor(qname, target); + } + Ok(None) + } + TyV_::Id(_, lhs, rhs) => { + // XXX trying to print as syntax, not values + self.equations.push((self.eval.quote_tm(lhs), self.eval.quote_tm(rhs))); + // self.equations.push((lhs.clone(), rhs.clone())); + Ok(None) + } + TyV_::Object(_) | TyV_::Sing(_, _) | TyV_::Unit | TyV_::Meta(_) => Ok(None), + } + } +} + +impl DiagramGenerator<'_, ModalDblModel, ModalDblModelMapping> { + fn generate(&mut self, body_ty: &TyV) -> Result { + let (self_n, eval_with_self) = self.eval.bind_self(body_ty.clone()); + self.eval = eval_with_self; + let self_val = self.eval.eta_neu(&self_n, body_ty); + Ok(self + .extract(vec![], &self_val, body_ty)? + .unwrap_or_else(Namespace::new_for_uuid)) + } + + fn extract( + &mut self, + prefix: Vec, + val: &TmV, + ty: &TyV, + ) -> Result, String> { + match &**ty { + TyV_::Record(r) => { + let mut namespace = Namespace::new_for_uuid(); + for (name, (label, _)) in r.fields.iter() { + let mut child_prefix = prefix.clone(); + child_prefix.push(*name); + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + let field_tm = self.eval.proj(val, *name, *label); + let field_ty = self.eval.field_ty(ty, val, *name); + if let Some(inner) = self.extract(child_prefix, &field_tm, &field_ty)? { + namespace.add_inner(*name, inner); + } + } + Ok(Some(namespace)) + } + TyV_::Over(path) => { + // Resolve the path against the codomain to find the + // catlog object type for this generator. + let (cod_self_n, cod_eval) = self.eval.bind_self(self.codomain_ty.clone()); + let cod_val = cod_eval.eta_neu(&cod_self_n, &self.codomain_ty); + let resolved = cod_eval.path_ty(&self.codomain_ty, &cod_val, path)?; + let TyV_::Object(ot) = &*resolved else { + return Err( + "@over path does not refer to an object generator in the codomain".into() + ); + }; + let ot: ModalObType = ot.clone().try_into().map_err(|_| { + "@over codomain object type is not valid for a discrete theory".to_string() + })?; + let qname: QualifiedName = prefix.into(); + self.domain.add_ob(qname.clone(), ot.into()); + let target: QualifiedName = + path.iter().map(|(seg, _)| *seg).collect::>().into(); + self.mapping.assign_ob(qname, target.into()); + Ok(None) + } + TyV_::Morphism(mt, dom, cod) => { + // Augmentation produces lift morphisms whose dom and cod + // are projections from the body's self, resolving to the + // qualified names of generators (declared or specialized). + let mt_inner: ModalMorType = mt + .clone() + .try_into() + .map_err(|_| "morphism type is not valid for a discrete theory".to_string())?; + let dom_ob: ModalOb = match &**dom { + TmV_::List(elems) => { + let src_ot: ModalObType = self.domain.theory().src_type(&mt_inner); // adapt: see note + let Some(modal::Modality::List(list_type)) = src_ot.modalities.last() + else { + // XXX + dbg!(&mt_inner); + return Err("multihom source is not a list object type".to_string()); + }; + let obs = elems + .iter() + .map(|e| neutral_qualified_name(e).map(ModalOb::Generator)) + .collect::>>() + .ok_or_else(|| { + "multihom domain element does not resolve to a generator name" + .to_string() + })?; + ModalOb::List(*list_type, obs) + } + TmV_::App(n, tm_v) => { + let ob_op = modal::ModalObOp::generator(QualifiedName::single(*n)); + let op_dom: ModalObType = self.domain.theory().ob_op_dom(&ob_op); + let Some(modal::Modality::List(list_type)) = op_dom.modalities.last() + else { + return Err("ob_op domain is not a list type".to_string()); + }; + match &**tm_v { + TmV_::List(elems) => { + let obs = elems + .iter() + .map(|e| neutral_qualified_name(e).map(ModalOb::Generator)) + .collect::>>() + .ok_or_else(|| "domain element does not resolve".to_string())?; + ModalOb::List(*list_type, obs) + } + _ => return Err("expected list inside ob_op".to_string()), + } + } + _ => ModalOb::Generator(neutral_qualified_name(dom).ok_or_else(|| { + "morphism domain does not resolve to a generator name".to_string() + })?), + }; + // + + let cod_ob: ModalOb = match &**cod { + TmV_::List(elems) => { + let src_ot: ModalObType = self.domain.theory().src_type(&mt_inner); // adapt: see note + let Some(modal::Modality::List(list_type)) = src_ot.modalities.last() + else { + // XXX + dbg!(&mt_inner); + return Err("multihom source is not a list object type".to_string()); + }; + let obs = elems + .iter() + .map(|e| neutral_qualified_name(e).map(ModalOb::Generator)) + .collect::>>() + .ok_or_else(|| { + "multihom domain element does not resolve to a generator name" + .to_string() + })?; + ModalOb::List(*list_type, obs) + } + TmV_::App(n, tm_v) => { + let ob_op = modal::ModalObOp::generator(QualifiedName::single(*n)); + let op_cod: ModalObType = self.domain.theory().ob_op_dom(&ob_op); + let Some(modal::Modality::List(list_type)) = op_cod.modalities.last() + else { + return Err("ob_op domain is not a list type".to_string()); + }; + match &**tm_v { + TmV_::List(elems) => { + let obs = elems + .iter() + .map(|e| neutral_qualified_name(e).map(ModalOb::Generator)) + .collect::>>() + .ok_or_else(|| "domain element does not resolve".to_string())?; + ModalOb::List(*list_type, obs) + } + _ => return Err("expected list inside ob_op".to_string()), + } + } + _ => ModalOb::Generator(neutral_qualified_name(cod).ok_or_else(|| { + "morphism domain does not resolve to a generator name".to_string() + })?), + }; + + let qname: QualifiedName = prefix.clone().into(); + self.domain.add_mor(qname.clone(), dom_ob.into(), cod_ob.into(), mt_inner); + // Mapping target: extract the codomain morphism's name + // from the synthetic lift name `~f(e)`. For other shapes + // (e.g., a future user-declared morphism in a body), fall + // back to the field name itself. + if let Some(last) = prefix.last() + && let Some(cod_mor) = parse_lift_morphism_name(last) + { + let target: modal::ModalMor = QualifiedName::single(cod_mor).into(); + self.mapping.assign_mor(qname, target); + } + Ok(None) + } + TyV_::Id(_, lhs, rhs) => { + self.equations.push((self.eval.quote_tm(lhs), self.eval.quote_tm(rhs))); + Ok(None) + } + + TyV_::Object(_) | TyV_::Sing(_, _) | TyV_::Unit | TyV_::Meta(_) => Ok(None), + } + } +} + +/// Read off a [`QualifiedName`] from a value that's a neutral chain of +/// projections from a self-variable (regardless of which level the self +/// is bound at). Returns `None` if the value isn't of that shape. +fn neutral_qualified_name(tm: &TmV) -> Option { + if let TmV_::List(elems) = &**tm { + // for e in elems { + // println!("{:?}", neutral_qualified_name(e)); + // } + }; + let TmV_::Neu(n, _) = &**tm else { + return None; + }; + Some(n.to_qualified_name()) +} + +fn neutral_qualified_names(tm: &TmV) -> Option> { + if let TmV_::List(elems) = &**tm { + for e in elems { + // println!("{:?}", neutral_qualified_name(e)); + } + }; + let TmV_::List(elems) = &**tm else { + return None; + }; + elems.into_iter().map(|n| neutral_qualified_name(n)).collect::>>() +} + +/// Parse a synthetic lift-morphism field name of the form `~f(e)` and +/// return the codomain morphism's name `f`. Returns `None` if the name +/// isn't of that shape. +fn parse_lift_morphism_name(name: &NameSegment) -> Option { + let NameSegment::Text(s) = name else { + return None; + }; + let s = s.as_str(); + let rest = s.strip_prefix('~')?; + let paren = rest.find('(')?; + Some(name_seg(ustr(&rest[..paren]))) +} + +#[cfg(test)] +mod tests { + use std::fs::read_to_string; + use std::path::PathBuf; + + use super::*; + use crate::dbl::model::FpDblModel; + use crate::tt::text_elab::{TT_PARSE_CONFIG, TopElabResult, TopElaborator}; + use crate::tt::theory::std_theories; + use crate::tt::toplevel::Toplevel; + use crate::zero::{Mapping, name}; + + fn elaborate_to_toplevel(src: &str) -> Toplevel { + let reporter = Reporter::new(); + let mut toplevel = Toplevel::new(std_theories()); + let _ = TT_PARSE_CONFIG.with_parsed_top(src, reporter.clone(), |topntns| { + let mut topelab = TopElaborator::new(reporter.clone()); + for topntn in topntns.iter() { + if let Some(TopElabResult::Declaration(name, decl)) = + topelab.elab(&toplevel, topntn) + { + // dbg!(&name); + toplevel.declarations.insert(name, decl); + } else { + // dbg!(&topntn.name); + } + } + Some(()) + }); + + if reporter.errored() { + let source_info = SourceInfo::new(None, src); + panic!("{}", source_info.extract_report_to_string(reporter.clone())); + } + + assert!(!reporter.errored(), "elaboration produced errors"); + toplevel + } + + #[test] + fn diagram_over_weighted_graph() { + let src = r#" +set_theory ThSchema + +type WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] + +diagram I : @Instance(WeightedGraph) := [ + e : @over .E, +] +"#; + let toplevel = elaborate_to_toplevel(src); + let diag = match toplevel.declarations.get(&name_seg("I")) { + Some(TopDecl::Diag(d)) => d.clone(), + _ => panic!("expected I to be a diagram declaration"), + }; + + let (model_diag, _, _) = + diagram_from_diag(&toplevel, &diag.theory.definition, &diag).unwrap(); + match model_diag { + DblModelDiagramType::Discrete(DblModelDiagram(mapping, domain)) => { + let e_qname: QualifiedName = vec![name_seg("e")].into(); + let entity_ot: QualifiedName = vec![name_seg("Entity")].into(); + let e_target: QualifiedName = vec![name_seg("E")].into(); + assert!(domain.has_ob(&e_qname)); + assert_eq!(domain.ob_generator_type(&e_qname), entity_ot); + assert_eq!(mapping.0.ob_generator_map.apply_to_ref(&e_qname), Some(e_target)); + } + DblModelDiagramType::ModalUnital(_) => { + panic!("We should not here!") + } + } + } + + #[test] + fn diagram_over_dec() { + // TODO breaks when i write "dot-u" and other hyphens + let src = r#" +set_theory ThMulticategory + +type DEC := [ + Form0 : Object, + Form1 : Object, + partial : Multihom[[Form0], Form0], + mult: Multihom[[Form0, Form0], Form0], + wedge00: Multihom[[Form0, Form0], Form0], + wedge10: Multihom[[Form1, Form0], Form1] +] + +diagram I : @Instance(DEC) := [ + v : @over .Form0, + u : @over .Form0, + w : @over .Form1, + result : (partial(u) == v) +] + +"#; + let toplevel = elaborate_to_toplevel(src); + let diag = match toplevel.declarations.get(&name_seg("I")) { + Some(TopDecl::Diag(d)) => d.clone(), + _ => panic!("expected I to be a diagram declaration"), + }; + + let (model_diag, _, _) = + diagram_from_diag(&toplevel, &diag.theory.definition, &diag).unwrap(); + match model_diag { + DblModelDiagramType::Discrete(_) => { + panic!("We should not be here!") + } + DblModelDiagramType::ModalUnital(DblModelDiagram(mapping, domain)) => { + let e_qname: QualifiedName = vec![name_seg("u")].into(); + let entity_ot = modal::ModeApp::new(name("Object")); + let e_target: QualifiedName = vec![name_seg("Form0")].into(); + assert!(domain.has_ob(&e_qname.clone().into())); + assert_eq!(domain.ob_generator_type(&e_qname), entity_ot); + assert_eq!( + mapping.0.ob_generator_map.apply_to_ref(&e_qname), + Some(e_target.into()) + ); + } + } + } + + #[test] + fn glueing_modal_diagrams() { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("examples/tt/text/test_klausmeier.dbltt"); + let Ok(src) = read_to_string(path) else { + panic!("Cannot read file") + }; + let toplevel = elaborate_to_toplevel(&src); + let diag = match toplevel.declarations.get(&name_seg("Klausmeier")) { + Some(TopDecl::Diag(d)) => d.clone(), + _ => panic!("expected I to be a diagram declaration"), + }; + + let (model_diag, _, _) = + diagram_from_diag(&toplevel, &diag.theory.definition, &diag).unwrap(); + let diagram = TextModel { + decl: "Klausmeier".into(), + toplevel: elaborate_to_toplevel(&src), + }; + let out = diagram.transpile(); + println!("{}", &out); + + match model_diag { + DblModelDiagramType::Discrete(_) => { + panic!("We should not be here!") + } + DblModelDiagramType::ModalUnital(DblModelDiagram(mapping, domain)) => { + let e_qname: QualifiedName = vec![name_seg("a")].into(); + let entity_ot = modal::ModeApp::new(name("Object")); + let e_target: QualifiedName = vec![name_seg("Form0")].into(); + assert!(domain.has_ob(&e_qname.clone().into())); + assert_eq!(domain.ob_generator_type(&e_qname), entity_ot); + assert_eq!( + mapping.0.ob_generator_map.apply_to_ref(&e_qname), + Some(e_target.into()) + ); + } } } } diff --git a/packages/catlog/src/tt/notebook_elab.rs b/packages/catlog/src/tt/notebook_elab.rs index f84623bb8..4f8938157 100644 --- a/packages/catlog/src/tt/notebook_elab.rs +++ b/packages/catlog/src/tt/notebook_elab.rs @@ -5,10 +5,10 @@ //! must be completely different to be well adapted to the notebook interface. //! As a first pass, we are associating cell UUIDs with errors. -use catcolab_document_types::current as nb; +use catcolab_document_types::{current as nb, v2::Mor}; use nonempty::NonEmpty; -use std::str::FromStr; -use uuid::Uuid; +use std::{fmt::Debug, str::FromStr}; +use uuid::{Uuid, uuid}; use super::{context::*, eval::*, prelude::*, stx::*, theory::*, toplevel::*, val::*}; use crate::dbl::{ @@ -78,7 +78,7 @@ impl<'a> Elaborator<'a> { v }; self.ctx.env = self.ctx.env.snoc(v.clone()); - self.ctx.scope.push(VarInContext::new(name, label, ty)); + self.ctx.scope.push(VarInContext::new(name, label, ty, VarKind::Term)); v } @@ -112,6 +112,27 @@ impl<'a> Elaborator<'a> { (name, label, ty_s, ty_v) } + fn diag_object_cell( + &mut self, + model: &RecordV, + ob_decl: &nb::DiagramObDecl, + ) -> (NameSegment, LabelSegment, TyS, TyV) { + let name = NameSegment::Uuid(ob_decl.id); + let label = LabelSegment::Text(ustr(&ob_decl.name)); + + let over_uuid = match &ob_decl.over { + Some(nb::Ob::Basic(id)) => id, + _ => panic!("expected basic"), + }; + let over_name = NameSegment::Uuid(Uuid::parse_str(&over_uuid).unwrap()); + let Some((_, (over_label, _))) = model.fields.iter().find(|(n, _)| *n == &over_name) else { + panic!("over reference not found in codomain model"); + }; + + let path = vec![(over_name, *over_label)]; + (name, label, TyS::over(path.clone()), TyV::over(path)) + } + fn lookup_tm(&self, name: VarName) -> Option<(TmS, TmV, TyV)> { let (i, label, ty) = self.ctx.lookup(name)?; let v = self.ctx.env.get(*i).unwrap().clone(); @@ -173,7 +194,7 @@ impl<'a> Elaborator<'a> { let name = QualifiedName::deserialize_str(name).unwrap(); let (stx, val, ty) = self.resolve_name(name.as_slice())?; let TyV_::Morphism(..) = &*ty else { - return None; + return None.unwrap(); }; Some((stx, val, ty)) } @@ -287,6 +308,132 @@ impl<'a> Elaborator<'a> { (name, label, ty_s, ty_v) } + fn diag_morphism_cell_ty( + &mut self, + model: &RecordV, + mor_decl: &nb::DiagramMorDecl, + ) -> (TyS, TyV) { + let over_uuid = match &mor_decl.over { + Some(nb::Mor::Basic(id)) => id, + _ => panic!("expected basic over reference"), + }; + let over_name = NameSegment::Uuid(Uuid::parse_str(&over_uuid).unwrap()); + let Some((_, (_, mor_ty_s))) = model.fields.iter().find(|(n, _)| *n == &over_name) else { + panic!("over morphism not found in codomain"); + }; + let TyS_::Morphism(mt, cod_dom_s, cod_cod_s) = &**mor_ty_s else { + panic!("over reference is not a morphism"); + }; + + let ob_op = match &**cod_dom_s { + TmS_::ObApp(op, _) => Some(*op), + _ => None, + }; + + let mut dom_stxs = Vec::new(); + let mut dom_vals = Vec::new(); + match &mor_decl.dom { + Some(nb::Ob::List { modality, objects }) => { + for ob in objects { + let id = match ob { + Some(nb::Ob::Basic(id)) => id, + _ => panic!(), + }; + let Some((s, v, _)) = + self.lookup_tm(NameSegment::Uuid(Uuid::parse_str(&id).unwrap())) + else { + panic!() + }; + dom_stxs.push(s); + dom_vals.push(v); + } + } + + Some(nb::Ob::Basic(id)) => { + let Some((s, v, _)) = + self.lookup_tm(NameSegment::Uuid(Uuid::parse_str(&id).unwrap())) + else { + panic!() + }; + dom_stxs.push(s); + dom_vals.push(v); + } + + _ => todo!(), + } + + let (dom_s, dom_v) = if let Some(op) = ob_op { + ( + TmS::ob_app(op, TmS::list(dom_stxs.clone())), + TmV::app(op, TmV::list(dom_vals.clone())), + ) + } else { + (TmS::list(dom_stxs.clone()), TmV::list(dom_vals.clone())) + }; + + let mut cod_stxs = Vec::new(); + let mut cod_vals = Vec::new(); + match &mor_decl.cod { + Some(nb::Ob::List { modality, objects }) => { + for ob in objects { + let id = match ob { + Some(nb::Ob::Basic(id)) => id, + _ => panic!(), + }; + let Some((s, v, _)) = + self.lookup_tm(NameSegment::Uuid(Uuid::parse_str(&id).unwrap())) + else { + panic!() + }; + cod_stxs.push(s); + cod_vals.push(v); + } + } + Some(nb::Ob::Basic(id)) => { + let Some((s, v, _)) = + self.lookup_tm(NameSegment::Uuid(Uuid::parse_str(&id).unwrap())) + else { + panic!("{}", id) + }; + cod_stxs.push(s); + cod_vals.push(v); + } + _ => panic!(), + } + + let (cod_s, cod_v) = if let Some(op) = ob_op { + ( + TmS::ob_app(op, TmS::list(cod_stxs.clone())), + TmV::app(op, TmV::list(cod_vals.clone())), + ) + } else { + (TmS::list(cod_stxs.clone()), TmV::list(cod_vals.clone())) + }; + + (TyS::morphism(mt.clone(), dom_s, cod_s), TyV::morphism(mt.clone(), dom_v, cod_v)) + } + + fn diag_morphism_cell( + &mut self, + model: &RecordV, + mor_decl: &nb::DiagramMorDecl, + ) -> (NameSegment, LabelSegment, TyS, TyV) { + let name = NameSegment::Uuid(mor_decl.id); + let label = LabelSegment::Text(ustr(&mor_decl.name)); + + let over_uuid = match &mor_decl.over { + Some(nb::Mor::Basic(id)) => id, + _ => panic!("expected basic"), + }; + let over_name = NameSegment::Uuid(Uuid::parse_str(&over_uuid).unwrap()); + let Some((_, (over_label, _))) = model.fields.iter().find(|(n, _)| *n == &over_name) else { + panic!("over reference not found in codomain model"); + }; + + let (ty_s, ty_v) = self.diag_morphism_cell_ty(model, mor_decl); + (name, over_label.clone(), ty_s, ty_v) + } + fn equation_cell_ty(&mut self, eqn_decl: &nb::EqnDecl) -> (TyS, TyV) { let (lhs_m, rhs_m) = match (&eqn_decl.lhs, &eqn_decl.rhs) { (Some(lhs), Some(rhs)) => (lhs, rhs), @@ -424,35 +571,122 @@ impl<'a> Elaborator<'a> { (name, label, ty_s, ty_v) } - /// Elaborate a notebook into a type. - pub fn notebook<'b>( + // XXX + fn diag_instantiation_cell_ty(&mut self, i_decl: &nb::InstantiatedDiagram) -> (TyS, TyV) { + let name = QualifiedName::single(NameSegment::Uuid(i_decl.id)); + let link = match &i_decl.diagram { + Some(l) => l, + None => return self.ty_error(InvalidDblModel::InvalidLink(name)), + }; + let catcolab_document_types::current::LinkType::Instantiation = link.r#type else { + return self.ty_error(InvalidDblModel::InvalidLink(name)); + }; + let ref_id = ustr(&link.stable_ref.id); + let topname = NameSegment::Text(ref_id); + let Some(TopDecl::Diag(diag_def)) = self.toplevel.declarations.get(&topname) else { + return self.ty_error(InvalidDblModel::InvalidLink(name)); + }; + // if type_def.theory != self.theory { + // return self.ty_error(InvalidDblModel::InvalidLink(name)); + // } + let mut specializations = Vec::new(); + let TyV_::Record(r) = &*diag_def.body_val else { + return self.ty_error(InvalidDblModel::InvalidLink(name)); + }; + let mut r = r.clone(); + for specialization in i_decl.specializations.iter() { + if let (Some(field_id), Some(ob)) = (&specialization.id, &specialization.ob) { + let field_name = NameSegment::Uuid(Uuid::from_str(field_id).unwrap()); + // let Some((ob_s, ob_v, ob_type)) = self.ob_syn(ob) else { + // println!("OB_SYN: {:#?}", ob); + // continue; + // }; + // let Some((field_label, field_ty)) = r.fields.get_with_label(field_name) else { + // println!("PASSING: {}", field_name); + // continue; + // }; + // match &**field_ty { + // TyS_::Object(expected_ob_ty) => { + // if &ob_type != expected_ob_ty { + // continue; + // } + // } + // _ => { + // continue; + // } + // } + let ob_name = match ob { + nb::Ob::Basic(id) => NameSegment::Uuid(Uuid::parse_str(id).unwrap()), + _ => continue, + }; + let Some((ob_s, ob_v, ob_ty)) = self.lookup_tm(ob_name) else { + continue; + }; + let Some((field_label, field_ty)) = r.fields.get_with_label(field_name) else { + continue; + }; + // println!("{}::{:#?}", field_ty, ob_ty); + match (&**field_ty, &*ob_ty) { + (TyS_::Over(_), TyV_::Over(path)) => { + specializations.push(( + vec![(field_name, *field_label)], + TyS::sing(TyS::over(path.clone()), ob_s), + )); + r = r.add_specialization( + &[(field_name, *field_label)], + TyV::sing(TyV::over(path.clone()), ob_v), + ); + } + _ => continue, + } + // specializations.push(( + // vec![(field_name, *field_label)], + // TyS::sing(TyS::object(ob_type.clone()), ob_s), + // )); + // r = r.add_specialization( + // &[(field_name, *field_label)], + // TyV::sing(TyV::object(ob_type), ob_v), + // ) + } + } + // dbg!(&specializations); + let ty_s = if specializations.is_empty() { + TyS::topvar(topname) + } else { + TyS::specialize(TyS::topvar(topname), specializations) + }; + (ty_s, TyV::record(r)) + } + + fn diag_instantiation_cell( &mut self, - cells: impl Iterator, + i_decl: &nb::InstantiatedDiagram, + ) -> (NameSegment, LabelSegment, TyS, TyV) { + let name = NameSegment::Uuid(i_decl.id); + let label = LabelSegment::Text(ustr(&i_decl.name)); + let (ty_s, ty_v) = self.diag_instantiation_cell_ty(i_decl); + (name, label, ty_s, ty_v) + } + + fn elaborate_cells<'b, T: 'b + Debug>( + &mut self, + cells: impl Iterator, + sort_key: impl Fn(&T) -> u8, + elab_cell: impl Fn(&mut Self, &T) -> (NameSegment, LabelSegment, TyS, TyV), ) -> (TyS, TyV) { - // Process the cells in dependency order. This is important because the - // UI allows users to reorder cells freely and that shouldn't affect the - // result of elaboration. let mut cells: Vec<_> = cells.collect(); - cells.sort_by_key(|judgment| match judgment { - nb::ModelJudgment::Object(_) => 0, - nb::ModelJudgment::Instantiation(_) => 1, - nb::ModelJudgment::Morphism(_) => 2, - nb::ModelJudgment::Equation(_) => 3, - }); + cells.sort_by_key(|j| sort_key(j)); let mut field_ty_vs = Vec::new(); let self_var = self.intro(name_seg("self"), label_seg("self"), None).unwrap_neu(); let c = self.checkpoint(); for cell in cells { - let (name, label, _, ty_v) = match &cell { - nb::ModelJudgment::Object(ob_decl) => self.object_cell(ob_decl), - nb::ModelJudgment::Morphism(mor_decl) => self.morphism_cell(mor_decl), - nb::ModelJudgment::Instantiation(i_decl) => self.instantiation_cell(i_decl), - nb::ModelJudgment::Equation(eqn_decl) => self.equation_cell(eqn_decl), - }; + let (name, label, _, ty_v) = elab_cell(self, cell); field_ty_vs.push((name, (label, ty_v.clone()))); - self.ctx.scope.push(VarInContext::new(name, label, Some(ty_v.clone()))); + self.ctx + .scope + .push(VarInContext::new(name, label, Some(ty_v.clone()), VarKind::Term)); self.ctx.env = self.ctx.env.snoc(TmV::neu(TmN::proj(self_var.clone(), name, label), ty_v)); } @@ -465,6 +699,52 @@ impl<'a> Elaborator<'a> { let r_v = RecordV::new(self.ctx.env.clone(), field_tys.clone(), Dtry::empty()); (TyS::record(field_tys), TyV::record(r_v)) } + + /// Elaborate a notebook into a type. + pub fn model_notebook<'b>( + &mut self, + cells: impl Iterator, + ) -> (TyS, TyV) { + self.elaborate_cells( + cells, + |j| match j { + nb::ModelJudgment::Object(_) => 0, + nb::ModelJudgment::Instantiation(_) => 1, + nb::ModelJudgment::Morphism(_) => 2, + nb::ModelJudgment::Equation(_) => 3, + }, + |elab, cell| match cell { + nb::ModelJudgment::Object(ob) => elab.object_cell(ob), + nb::ModelJudgment::Morphism(mor) => elab.morphism_cell(mor), + nb::ModelJudgment::Instantiation(i) => elab.instantiation_cell(i), + nb::ModelJudgment::Equation(eq) => elab.equation_cell(eq), + }, + ) + } + + /// Elaborate a diagram notebook into a type. + pub fn diagram_notebook<'b>( + &mut self, + model: TyV, + cells: impl Iterator, + ) -> (TyS, TyV) { + let TyV_::Record(r) = &*model else { panic!() }; + self.elaborate_cells( + cells, + |j| match j { + nb::DiagramJudgment::Object(_) => 0, + nb::DiagramJudgment::Instantiation(_) => 1, + nb::DiagramJudgment::Morphism(_) => 1, + nb::DiagramJudgment::Equation(_) => 2, + }, + |elab, cell| match cell { + nb::DiagramJudgment::Object(ob) => elab.diag_object_cell(r, ob), + nb::DiagramJudgment::Morphism(mor) => elab.diag_morphism_cell(r, mor), + nb::DiagramJudgment::Instantiation(i) => elab.diag_instantiation_cell(i), + nb::DiagramJudgment::Equation(_) => todo!(), // elab.equation_cell(eq), + }, + ) + } } /// Promotes a modality from notebook type to modality for modal theory. @@ -497,20 +777,29 @@ pub fn demote_modality(modality: modal::Modality) -> nb::Modality { #[cfg(test)] mod test { + use catcolab_document_types::v1::DiagramDocumentContent; use expect_test::{Expect, expect}; use serde_json; + use std::any::Any; use std::{fmt::Write, fs}; use ustr::ustr; use crate::dbl::model::DblModelPrinter; - use crate::stdlib::{th_schema, th_sym_monoidal_category}; + use crate::dbl::model_diagram::DblModelDiagram; + use crate::stdlib::{th_multicategory, th_schema, th_sym_monoidal_category}; + use crate::tt::eval::Evaluator; + use crate::tt::modelgen::diagram_from_diag; + use crate::tt::stx::TyS_; + use crate::tt::toplevel::{Diag, TopDecl}; + use crate::tt::util::{Decapodes, JuliaTranspiler}; + use crate::tt::val::{TmV_, TyV_}; use crate::tt::{ modelgen::Model, notebook_elab::Elaborator, theory::{Theory, TheoryDef}, toplevel::Toplevel, }; - use crate::zero::name; + use crate::zero::{NameSegment, name}; use catcolab_document_types::current::ModelDocumentContent; fn elab_example(theory: &Theory, name: &str, expected: Expect) -> Model { @@ -518,7 +807,7 @@ mod test { let doc: ModelDocumentContent = serde_json::from_str(&src).unwrap(); let toplevel = Toplevel::new(Default::default()); let mut elab = Elaborator::new(theory.clone(), &toplevel, ustr("")); - let (_, ty_v) = elab.notebook(doc.notebook.formal_content()); + let (_, ty_v) = elab.model_notebook(doc.notebook.formal_content()); let (model, ns) = Model::from_ty(&toplevel, &theory.definition, &ty_v); let mut out = model.to_doc(&DblModelPrinter::new(), &ns).pretty().to_string(); for error in elab.errors() { @@ -601,4 +890,196 @@ mod test { let eqns: Vec<_> = model.category.equations().collect(); assert_eq!(eqns.len(), 1); } + + /// Heat equation in the guise of a petri net + #[test] + fn heat_eq() { + // LOAD THE MODEL + let th_petri = + Theory::new(name("ThPetri"), TheoryDef::modal_unital(th_sym_monoidal_category())); + let src = + fs::read_to_string(format!("examples/tt/notebook/heat_eq_petri_model.json")).unwrap(); + let doc: ModelDocumentContent = serde_json::from_str(&src).unwrap(); + let toplevel = Toplevel::new(Default::default()); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (_, model_ty_v) = elab.model_notebook(doc.notebook.formal_content()); + + // LOAD THE DIAGRAM + let src = fs::read_to_string(format!("examples/tt/notebook/heat_eq_petri.json")).unwrap(); + let doc: DiagramDocumentContent = serde_json::from_str(&src).unwrap(); + let toplevel = Toplevel::new(Default::default()); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (ty_s, ty_v) = elab.diagram_notebook(model_ty_v.clone(), doc.notebook.formal_content()); + + // The diagram is now a record type value. we must convert it to string + let TyV_::Record(r) = &*ty_v else { panic!() }; + + let TyV_::Record(r) = &*ty_v else { panic!() }; + let eval = Evaluator::empty(&toplevel); + let (self_n, eval) = eval.bind_self(ty_v.clone()); + let self_v = eval.eta_neu(&self_n, &ty_v); + for (name, (label, _)) in r.fields.iter() { + let field_ty = eval.field_ty(&ty_v, &self_v, *name); + // let field_tm = eval.proj(&self_v, *name, *label); + + match &*field_ty { + TyV_::Over(path) => { + // Object declaration: name is the generator, path is the codomain object + println!("ob: {} : @over {:?}", label, path); + } + TyV_::Morphism(mt, dom, cod) => { + // dom/cod are TmV — could be App("tensor", List([...])) or Neu(...) + match (&**dom, &**cod) { + (TmV_::App(op, dom_args), TmV_::App(_, cod_args)) => { + // ob_op morphism: e.g. tensor([u]) -> tensor([partial_u]) + println!( + "mor (op {}): {} -> {}", + op, + eval.quote_tm(dom), + eval.quote_tm(cod) + ); + } + _ => { + // plain morphism + println!("mor: {} -> {}", eval.quote_tm(dom), eval.quote_tm(cod)); + } + } + } + TyV_::Id(_, lhs, rhs) => { + println!("eq: {} == {}", eval.quote_tm(lhs), eval.quote_tm(rhs)); + } + _ => {} // Unit, Sing, Meta, Record (sub-diagrams), etc. + } + } + + // + let over_decls: Vec<_> = r + .fields + .iter() + .filter_map(|(name, (label, ty_s))| { + if let TyS_::Over(path) = &**ty_s { + let names = path.iter().map(|(n, _)| *n).collect(); + Some((vec![*name], (*label, names))) + } else { + None + } + }) + .collect(); + + // old news + let (model, ns) = Model::from_ty(&toplevel, &th_petri.definition, &ty_v); + let diag = Diag::new(th_petri.clone(), model_ty_v.clone(), ty_s, ty_v, over_decls); + + let (model_diag, _, _) = diagram_from_diag(&toplevel, &th_petri.definition, &diag).unwrap(); + + let (mapping, domain) = match model_diag { + crate::dbl::model_diagram::DblModelDiagramType::ModalUnital(DblModelDiagram( + mapping, + domain, + )) => (mapping, domain), + _ => todo!(), + }; + + // let mut out = domain.to_doc(&DblModelPrinter::new(), &ns).pretty().to_string(); + // dbg!(&out); + } + + /// Klausmeier + #[test] + fn klausmeier() { + // LOAD THE MODEL + let th_petri = Theory::new(name("ThDEC"), TheoryDef::modal_unital(th_multicategory())); + let src = + fs::read_to_string(format!("examples/tt/notebook/klausmeier/model_dec_fragment.json")) + .unwrap(); + let doc: ModelDocumentContent = serde_json::from_str(&src).unwrap(); + let mut toplevel = Toplevel::new(Default::default()); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (_, model_ty_v) = elab.model_notebook(doc.notebook.formal_content()); + + let hydro_src = + fs::read_to_string(format!("examples/tt/notebook/klausmeier/hydrodynamics.json")) + .unwrap(); + let hydro_doc: DiagramDocumentContent = serde_json::from_str(&hydro_src).unwrap(); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (hydro_s, hydro_v) = + elab.diagram_notebook(model_ty_v.clone(), hydro_doc.notebook.formal_content()); + // Store under the document ID that Klausmeier's instantiation references + let hydro_doc_id = "019eb37e-eb26-7283-8c68-63d4cb8cd1f7"; // from Klausmeier.json + toplevel.declarations.insert( + NameSegment::Text(ustr(hydro_doc_id)), + TopDecl::Diag(Diag::new( + th_petri.clone(), + model_ty_v.clone(), + hydro_s, + hydro_v, + vec![], + )), + ); + + let phyto_src = + fs::read_to_string(format!("examples/tt/notebook/klausmeier/phytodynamics.json")) + .unwrap(); + let phyto_doc: DiagramDocumentContent = serde_json::from_str(&phyto_src).unwrap(); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (phyto_s, phyto_v) = + elab.diagram_notebook(model_ty_v.clone(), phyto_doc.notebook.formal_content()); + // Store under the document ID that Klausmeier's instantiation references + let phyto_doc_id = "019eb288-c310-7f33-b2c3-171279589942"; + // let phyto_doc_id = "019eb916-9299-7489-836f-80bd1830f913"; // from Klausmeier.json + toplevel.declarations.insert( + NameSegment::Text(ustr(phyto_doc_id)), + TopDecl::Diag(Diag::new( + th_petri.clone(), + model_ty_v.clone(), + phyto_s, + phyto_v, + vec![], + )), + ); + + // LOAD THE DIAGRAM + let src = + fs::read_to_string(format!("examples/tt/notebook/klausmeier/Klausmeier.json")).unwrap(); + let doc: DiagramDocumentContent = serde_json::from_str(&src).unwrap(); + // let toplevel = Toplevel::new(Default::default()); + let mut elab = Elaborator::new(th_petri.clone(), &toplevel, ustr("")); + let (_, ty_v) = elab.diagram_notebook(model_ty_v.clone(), doc.notebook.formal_content()); + + let pode = Decapodes { pode: ty_v }; + let out = pode.transpile(); + println!("{}", &out); + + // The diagram is now a record type value. we must convert it to string + // let TyV_::Record(r) = &*ty_v else { panic!() }; + // let eval = Evaluator::empty(&toplevel); + // let (self_n, eval) = eval.bind_self(ty_v.clone()); + // let self_v = eval.eta_neu(&self_n, &ty_v); + // for (name, (label, _)) in r.fields.iter() { + // let field_ty = eval.field_ty(&ty_v, &self_v, *name); + + // match &*field_ty { + // TyV_::Over(path) => { + // println!("ob: {} : @over {:?}", label, path); + // } + // TyV_::Morphism(_, dom, cod) => match (&**dom, &**cod) { + // (TmV_::App(op, _), TmV_::App(_, _)) => { + // println!( + // "mor (op {}): {} -> {}", + // op, + // eval.quote_tm(dom), + // eval.quote_tm(cod) + // ); + // } + // _ => { + // println!("{}: {} -> {}", label, eval.quote_tm(dom), eval.quote_tm(cod)); + // } + // }, + // TyV_::Id(_, lhs, rhs) => { + // println!("eq: {} == {}", eval.quote_tm(lhs), eval.quote_tm(rhs)); + // } + // _ => {} + // } + // } + } } diff --git a/packages/catlog/src/tt/stx.rs b/packages/catlog/src/tt/stx.rs index e01fc7886..fca884796 100644 --- a/packages/catlog/src/tt/stx.rs +++ b/packages/catlog/src/tt/stx.rs @@ -15,7 +15,7 @@ use crate::zero::LabelSegment; /// requested with `@hole`. /// /// Metavariables in notebook elaboration are namespaced to the notebook. -#[derive(Constructor, Clone, Copy, PartialEq, Eq)] +#[derive(Constructor, Debug, Clone, Copy, PartialEq, Eq)] pub struct MetaVar { ref_id: Option, id: usize, @@ -28,6 +28,7 @@ impl fmt::Display for MetaVar { } /// Inner enum for [TyS]. +#[derive(Debug)] pub enum TyS_ { /// A reference to a top-level declaration. TopVar(TopVarName), @@ -94,13 +95,24 @@ pub enum TyS_ { /// Currently, this is only used for handling elaboration errors, we might /// add more unification/holes later. Meta(MetaVar), + + /// The type of terms in a fiber over an object generator of some + /// diagram's codomain model. + /// + /// The path identifies the object generator in the codomain. Diagram + /// identity is contextual rather than part of the type: any diagram + /// in scope contributes its terms to this type, so two `@over .V` + /// types from different diagram declarations are convertible. The + /// elaborator validates the path against the enclosing diagram's + /// codomain at construction time. Example surface syntax: `e : @over .E`. + Over(Vec<(FieldName, LabelSegment)>), } /// Syntax for total types, dereferences to [TyS_]. /// /// See [crate::tt] for an explanation of what total types are, and for an /// explanation of our approach to Rc pointers in abstract syntax trees. -#[derive(Clone, Deref)] +#[derive(Clone, Debug, Deref)] #[deref(forward)] pub struct TyS(Rc); @@ -152,6 +164,11 @@ impl TyS { pub fn meta(mv: MetaVar) -> Self { Self(Rc::new(TyS_::Meta(mv))) } + + /// Smart constructor for [TyS], [TyS_::Over] case. + pub fn over(path: Vec<(FieldName, LabelSegment)>) -> Self { + Self(Rc::new(TyS_::Over(path))) + } } impl ToDoc for TyS { @@ -176,6 +193,7 @@ impl ToDoc for TyS { ), TyS_::Unit => t("Unit"), TyS_::Meta(mv) => t(format!("?{}", mv.id)), + TyS_::Over(path) => t(format!("@over{}", path_to_string(path))), } } } @@ -195,6 +213,7 @@ impl fmt::Display for TyS { } /// Inner enum for [TmS]. +#[derive(Debug)] pub enum TmS_ { /// A reference to a top-level constant def. TopVar(TopVarName), @@ -233,7 +252,7 @@ pub enum TmS_ { /// /// See [crate::tt] for an explanation of what total types are, and for an /// explanation of our approach to Rc pointers in abstract syntax trees. -#[derive(Clone, Deref)] +#[derive(Clone, Debug, Deref)] #[deref(forward)] pub struct TmS(Rc); diff --git a/packages/catlog/src/tt/text_elab.rs b/packages/catlog/src/tt/text_elab.rs index 64532fc95..c6fddf587 100644 --- a/packages/catlog/src/tt/text_elab.rs +++ b/packages/catlog/src/tt/text_elab.rs @@ -1,6 +1,7 @@ //! Elaboration from plain text for DoubleTT. use fnotation::*; +use itertools::Itertools; use scopeguard::{ScopeGuard, guard}; use fnotation::{ParseConfig, parser::Prec}; @@ -24,7 +25,7 @@ pub const TT_PARSE_CONFIG: ParseConfig = ParseConfig::new( ("==", Prec::nonassoc(30)), ], &[":", ":=", "&", "Unit", "Hom", "*", "=="], - &["type", "def", "syn", "chk", "norm", "generate", "uwd", "set_theory"], + &["type", "def", "syn", "chk", "norm", "generate", "uwd", "set_theory", "diagram"], ); /// The result of elaborating a top-level statement. @@ -124,11 +125,71 @@ impl TopElaborator { ) })?; let (ty_s, ty_v) = self.elaborator(&theory, toplevel).ty(ty_n); + // println!("TYPE: {}, \n\n {}", ty_s, ty_n); Some(TopElabResult::Declaration( name, TopDecl::Type(Type::new(theory.clone(), ty_s, ty_v)), )) } + "diagram" => { + let theory = self.get_theory(tn.loc)?; + let mut elab = self.elaborator(&theory, toplevel); + let (name, args_n, annotn, valn) = self.annotated_def(tn.body).or_else(|| { + self.error( + tn.loc, + "unknown syntax for diagram declaration, expected : := ", + ) + })?; + if args_n.is_some() { + return self.error(tn.loc, "diagrams cannot have arguments"); + } + let (_, ret_ty_v) = elab.ty(annotn); + // Bind the diagram's name as a diagram-kind context entry + // so that `@over .E` inside the body can recover both the + // diagram's name and its codomain type, while ordinary + // term lookups cannot see it. + let diag_label = match name { + NameSegment::Text(s) => label_seg(s), + NameSegment::Uuid(_) => label_seg("diag"), + }; + elab.intro_diagram(name, diag_label, ret_ty_v.clone()); + + let mut sub_diag_fields = Vec::new(); + if let Tuple(field_ns) = valn.ast0() { + for field_n in field_ns.iter() { + if let App2(L(_, Keyword(":")), L(_, Var(fname)), L(_, Var(tname))) = + field_n.ast0() + { + let tname = name_seg(*tname); + if let Some(TopDecl::Diag(_)) = elab.toplevel.lookup(tname) { + sub_diag_fields.push((name_seg(*fname), tname)); + } + } + } + } + + let (val_s, _val_v) = elab.ty(valn); + // Augment the body with synthetic lift-target fields forced + // by the discrete-opfibration condition: for each declared + // `e : @over .X` and codomain morphism `f : X → Y`, add a + // synthetic field `f(e) : @over .Y`. + let (val_s, mut over_decls) = augment_with_lifts(&val_s, &ret_ty_v); + + for (field_name, diag_name) in &sub_diag_fields { + if let Some(TopDecl::Diag(sub)) = elab.toplevel.lookup(*diag_name) { + for (sub_path, info) in &sub.over_decls { + let mut full = vec![*field_name]; + full.extend(sub_path.iter().copied()); + over_decls.push((full, info.clone())); + } + } + } + let val_v = elab.evaluator().eval_ty(&val_s); + Some(TopElabResult::Declaration( + name, + TopDecl::Diag(Diag::new(theory.clone(), ret_ty_v, val_s, val_v, over_decls)), + )) + } "def" => { let theory = self.get_theory(tn.loc)?; let (name, args_n, ty_n, tm_n) = self.annotated_def(tn.body).or_else(|| { @@ -352,6 +413,15 @@ impl<'a> Elaborator<'a> { v } + /// Like [`Self::intro`], but pushes a diagram-kind binding so it is + /// invisible to ordinary term lookup. + fn intro_diagram(&mut self, name: VarName, label: LabelSegment, ty: TyV) { + let v = TmV::neu(TmN::var(self.ctx.scope.len().into(), name, label), ty.clone()); + let v = self.evaluator().eta(&v, Some(&ty)); + self.ctx.env = self.ctx.env.snoc(v); + self.ctx.push_diagram(name, label, ty); + } + fn binding(&mut self, n: &FNtn) -> Option<(VarName, LabelSegment, TyS, TyV)> { let mut elab = self.enter(n.loc()); match n.ast0() { @@ -379,6 +449,19 @@ impl<'a> Elaborator<'a> { )) } } + TopDecl::Diag(d) => { + if d.theory == self.theory { + // Using a diagram name as a type yields the diagram's + // body record, allowing `we : I & [...]`-style + // specializations to walk paths through I's fields. + (TyS::topvar(name), d.body_val.clone()) + } else { + self.ty_error(format!( + "{name} refers to a diagram in theory {}, expected theory {}", + d.theory, self.theory + )) + } + } TopDecl::Def(_) | TopDecl::DefConst(_) => { self.ty_error(format!("{name} refers to a term not a type")) } @@ -387,7 +470,6 @@ impl<'a> Elaborator<'a> { self.ty_error(format!("no such type {name} defined")) } } - fn morphism_ty(&mut self, n: &FNtn) -> Option<(MorType, ObType, ObType)> { let elab = self.enter(n.loc()); let theory = elab.theory(); @@ -406,9 +488,10 @@ impl<'a> Elaborator<'a> { } Var(name) => { let qname = QualifiedName::single(name_seg(*name)); - if let Some(mor_type) = theory.basic_mor_type(qname) { + if let Some(mor_type) = theory.basic_mor_type(qname.clone()) { let dom = theory.src_type(&mor_type); let cod = theory.tgt_type(&mor_type); + // println!("MOR {} TYPE {} : DOM {} => COD {}", &qname, &mor_type, &dom, &cod); Some((mor_type, dom, cod)) } else { elab.error(format!("no such morphism type {name}")) @@ -427,6 +510,23 @@ impl<'a> Elaborator<'a> { p.push((name_seg(*f), label_seg(*f))); Some(p) } + // `.f(x)` — applied path segment, resolved to a single synthetic + // field whose name is the canonical string `"f(x)"`. This makes + // the lift-target objects forced by the discrete-opfibration + // condition addressable by ordinary path-walking. + App1(head_n, L(_, Var(x))) => match head_n.ast0() { + Field(f) => { + let s = ustr(&format!("{f}({x})")); + Some(vec![(name_seg(s), label_seg(s))]) + } + App1(p_n, L(_, Field(f))) => { + let mut p = elab.path(p_n)?; + let s = ustr(&format!("{f}({x})")); + p.push((name_seg(s), label_seg(s))); + Some(p) + } + _ => elab.error("unexpected notation for path"), + }, _ => elab.error("unexpected notation for path"), } } @@ -452,6 +552,7 @@ impl<'a> Elaborator<'a> { /// Elaborates a type from notation, returning both syntax and value. pub fn ty(&mut self, n: &FNtn) -> (TyS, TyV) { let mut elab = self.enter(n.loc()); + // println!("ELABORATING TYPE... {}", &n); match n.ast0() { Var(name) => elab.lookup_ty(name_seg(*name)), Keyword("Unit") => (TyS::unit(), TyV::unit()), @@ -459,6 +560,34 @@ impl<'a> Elaborator<'a> { let (tm_s, tm_v, ty_v) = elab.syn(tm_n); (TyS::sing(elab.evaluator().quote_ty(&ty_v), tm_s), TyV::sing(ty_v, tm_v)) } + // `@Instance(X)` is a structural alias for `X`, used in diagram + // annotations. Treating it as an alias keeps the type system + // simple; `I` is kept out of term lookup by the diagram-kind + // binding pushed in the `diagram` toplevel arm. + App1(L(_, Prim("Instance")), x_n) => elab.ty(x_n), + App1(L(_, Prim("over")), p_n) => { + let Some(path) = elab.path(p_n) else { + return elab.ty_hole(); + }; + // The enclosing diagram-kind binding gives the codomain + // against which we validate the path. The diagram's + // identity is not stored in the resulting type — `@over` + // names a fiber-type that's shared across diagrams. + let Some((_, model_ty)) = elab.ctx.lookup_diagram() else { + return elab.ty_error("@over is only allowed inside a diagram declaration"); + }; + let evaluator = elab.evaluator(); + let (self_n, _) = evaluator.bind_self(model_ty.clone()); + let self_val = evaluator.eta_neu(&self_n, &model_ty); + let resolved = match evaluator.path_ty(&model_ty, &self_val, &path) { + Ok(t) => t, + Err(msg) => return elab.ty_error(msg), + }; + let TyV_::Object(_) = &*resolved else { + return elab.ty_error("path does not refer to an object generator"); + }; + (TyS::over(path.clone()), TyV::over(path)) + } App1(mt_n, L(_, Tuple(domcod_n))) => { let [dom_n, cod_n] = domcod_n.as_slice() else { return elab.ty_error("expected two arguments for morphism type"); @@ -533,10 +662,14 @@ impl<'a> Elaborator<'a> { App2(L(_, Keyword("==")), tm1_n, tm2_n) => { let (tm1_s, tm1_v, tm1_ty) = elab.syn(tm1_n); let (tm2_s, tm2_v, tm2_ty) = elab.syn(tm2_n); - let TyV_::Morphism(_, _, _) = &*tm1_ty else { - elab.loc = Some(tm1_n.loc()); - return elab.ty_error("Equality types are only supported for morphisms"); + let _ = match &*tm1_ty { + TyV_::Morphism(_, _, _) | TyV_::Over(_) => {} + _ => { + elab.loc = Some(tm1_n.loc()); + return elab.ty_error("Equality types are only supported for morphisms"); + } }; + if let Err(e) = elab.evaluator().convertible_ty(&tm1_ty, &tm2_ty) { let eval = elab.evaluator(); return elab.ty_error(format!( @@ -568,6 +701,9 @@ impl<'a> Elaborator<'a> { TopDecl::Type(_) => self.syn_error(format!("{name} refers type, not term")), TopDecl::DefConst(d) => (TmS::topvar(name), d.val.clone(), d.ty.clone()), TopDecl::Def(_) => self.syn_error(format!("{name} must be applied to arguments")), + TopDecl::Diag(_) => { + self.syn_error(format!("{name} refers to a diagram, not a term")) + } } } else { self.syn_error(format!("no such variable {name}")) @@ -667,26 +803,55 @@ impl<'a> Elaborator<'a> { } App1(L(_, Var(tv)), L(_, Tuple(args_n))) => { let tv = name_seg(*tv); - let Some(TopDecl::Def(d)) = elab.toplevel.lookup(tv) else { - return elab.syn_error(format!("no such toplevel def {tv}")); - }; - let mut arg_stxs = Vec::new(); - let mut env = Env::nil(); - if args_n.len() != d.args.len() { - return elab.syn_error(format!( - "wrong number of args for {tv}, expected {}, got {}", - d.args.len(), - args_n.len() - )); - } - for (arg_n, (_, (_, arg_ty_s))) in args_n.iter().zip(d.args.iter()) { - let arg_ty_v = elab.evaluator().with_env(env.clone()).eval_ty(arg_ty_s); - let (arg_s, arg_v) = elab.chk(&arg_ty_v, arg_n); - arg_stxs.push(arg_s); - env = env.snoc(arg_v); + if let Some(TopDecl::Def(d)) = elab.toplevel.lookup(tv) { + // ---- existing toplevel-def path, unchanged ---- + let mut arg_stxs = Vec::new(); + let mut env = Env::nil(); + if args_n.len() != d.args.len() { + return elab.syn_error(format!( + "wrong number of args for {tv}, expected {}, got {}", + d.args.len(), + args_n.len() + )); + } + for (arg_n, (_, (_, arg_ty_s))) in args_n.iter().zip(d.args.iter()) { + let arg_ty_v = elab.evaluator().with_env(env.clone()).eval_ty(arg_ty_s); + let (arg_s, arg_v) = elab.chk(&arg_ty_v, arg_n); + arg_stxs.push(arg_s); + env = env.snoc(arg_v); + } + let eval = elab.evaluator().with_env(env.clone()); + (TmS::topapp(tv, arg_stxs), eval.eval_tm(&d.body), eval.eval_ty(&d.ret_ty)) + // TODO document this + } else if let Some((_, codomain_ty)) = elab.ctx.lookup_diagram() + && let TyV_::Record(cod_r) = &*codomain_ty + && let Some((_, (_, mor_ty_s))) = cod_r.fields.iter().find(|(n, _)| **n == tv) + && let TyS_::Morphism(_mt, dom_s, cod_s) = &**mor_ty_s + && let Some(dom_paths) = term_to_paths(dom_s) + && let Some(cod_path) = tms_to_path(cod_s) + { + if args_n.len() != dom_paths.len() { + return elab.syn_error(format!( + "{tv}: expected {} args, got {}", + dom_paths.len(), + args_n.len() + )); + } + let mut arg_stxs = Vec::new(); + let mut arg_vals = Vec::new(); + for (arg_n, expected_path) in args_n.iter().zip(dom_paths.iter()) { + let (arg_s, arg_v) = elab.chk(&TyV::over(expected_path.clone()), arg_n); + arg_stxs.push(arg_s); + arg_vals.push(arg_v); + } + ( + TmS::ob_app(tv, TmS::list(arg_stxs)), + TmV::app(tv, TmV::list(arg_vals)), + TyV::over(cod_path), + ) + } else { + elab.syn_error(format!("no such toplevel def {tv}")) } - let eval = elab.evaluator().with_env(env.clone()); - (TmS::topapp(tv, arg_stxs), eval.eval_tm(&d.body), eval.eval_ty(&d.ret_ty)) } Tag("tt") => (TmS::tt(), TmV::tt(), TyV::unit()), Tuple(_) => elab.syn_error("must check against a type in order to construct a record"), @@ -774,6 +939,191 @@ impl<'a> Elaborator<'a> { } } +/// Augment a diagram's body with synthetic lift-target fields. +/// +/// For each declared `e : @over .X` field, and each codomain morphism +/// `f : X → Y`, add a synthetic field named `"f(e)" : @over .Y`. Iterates +/// to a fixpoint, with a depth bound to guard against cyclic theories. +/// +/// The synthetic fields exist only in the type-theoretic body so that +/// path-walking and specialization (e.g., `we : I & [.src(e) := v1]`) +/// can resolve `.src(e)` as an ordinary field. Modelgen filters them +/// out by name when extracting the domain model. +fn augment_with_lifts( + body_s: &TyS, + codomain: &TyV, +) -> (TyS, Vec<(Vec, (LabelSegment, Vec))>) { + const MAX_DEPTH: usize = 16; + let TyS_::Record(initial) = &**body_s else { + return (body_s.clone(), vec![]); + }; + let TyV_::Record(cod_r) = &**codomain else { + return (body_s.clone(), vec![]); + }; + + // Collect (mor_name, mt, dom_path, cod_path) for each codomain + // morphism field whose source/target reduce to a single chain of + // fields. + type LiftPath = Vec<(NameSegment, LabelSegment)>; + // TODO assuming List for now + type CodMor = (NameSegment, MorType, Vec, LiftPath); + let mut cod_morphisms: Vec = Vec::new(); + for (mor_name, (_, mor_ty_s)) in cod_r.fields.iter() { + if let TyS_::Morphism(mt, dom_s, cod_s) = &**mor_ty_s + // XXX replace tms_to_path + && let (Some(dom_path), Some(cod_path)) = (term_to_paths(dom_s), tms_to_path(cod_s)) + { + cod_morphisms.push((*mor_name, mt.clone(), dom_path, cod_path)); + } + } + + let over_declarations = initial + .iter() + .filter_map(|(fname, (flabel, fty))| match &**fty { + TyS_::Over(path) => { + let names: Vec<_> = path.iter().map(|(n, _)| *n).collect(); + Some((vec![fname.clone()], (flabel.clone(), names))) + } + _ => None, + }) + .collect::>(); + + let mut row = initial.clone(); + for _ in 0..1 { + let snapshot = row.clone(); + let mut added = false; + + // path (of an @over field's target) -> declared fields over that path + let mut fields_by_path: HashMap> = + HashMap::new(); + for (fname, (flabel, fty)) in snapshot.iter() { + if let TyS_::Over(p) = &**fty { + fields_by_path.entry(p.clone()).or_default().push((*fname, *flabel)); + } + } + + for (mor_name, mt, dom_path, cod_path) in &cod_morphisms { + match mt { + MorType::Modal(_) => { + let Some(per_slot): Option>> = + dom_path.iter().map(|slot| fields_by_path.get(slot)).collect() + else { + continue; + }; + + // let Some(per_slot) = per_slot else { continue }; // a slot with no candidate object + // ( (u, u), (v, v) ) + for tuple in + per_slot.into_iter().map(|v| v.iter().copied()).multi_cartesian_product() + { + let args = + tuple.iter().map(|(n, _)| n.to_string()).collect::>().join(", "); + let synth = ustr(&format!("{mor_name}({args})")); + let synth_seg = name_seg(synth); + let synth_label = label_seg(synth); + if !row.has(synth_seg) { + row.insert(synth_seg, synth_label, TyS::over(cod_path.clone())); + added = true; + } + let lift_name = ustr(&format!("~{mor_name}({args})")); + let lift_seg = name_seg(lift_name); + let lift_label = label_seg(lift_name); + if !row.has(lift_seg) { + let self_var = + TmS::var(BwdIdx::from(0), name_seg("self"), label_seg("self")); + let dom_elems: Vec = tuple + .iter() + .map(|(n, l)| TmS::proj(self_var.clone(), *n, *l)) + .collect(); + let dom_term = TmS::list(dom_elems); + let cod_term = TmS::proj(self_var, synth_seg, synth_label); + row.insert( + lift_seg, + lift_label, + TyS::morphism(mt.clone(), dom_term, cod_term), + ); + added = true; + } + } + } + _ => { + // A discrete morphism's domain is a single object, so exactly one slot. + let [slot] = &dom_path[..] else { + continue; + }; + let Some(candidates) = fields_by_path.get(slot) else { + continue; + }; + for &(field_name, field_label) in candidates { + // Synthetic object field `f(e) : @over .Y`, the unique lift + // target forced by the discrete-opfibration condition. + let synth = ustr(&format!("{mor_name}({field_name})")); + let synth_seg = name_seg(synth); + let synth_label = label_seg(synth); + if !row.has(synth_seg) { + row.insert(synth_seg, synth_label, TyS::over(cod_path.clone())); + added = true; + } + // Synthetic morphism field `~f(e) : Morphism(mt, e, f(e))`. + let lift_name = ustr(&format!("~{mor_name}({field_name})")); + let lift_seg = name_seg(lift_name); + let lift_label = label_seg(lift_name); + if !row.has(lift_seg) { + let self_var = + TmS::var(BwdIdx::from(0), name_seg("self"), label_seg("self")); + let dom_term = TmS::proj(self_var.clone(), field_name, field_label); + let cod_term = TmS::proj(self_var, synth_seg, synth_label); + row.insert( + lift_seg, + lift_label, + TyS::morphism(mt.clone(), dom_term, cod_term), + ); + added = true; + } + } + } + } + } + if !added { + return (TyS::record(row), vec![]); + } + } + // Hit the depth bound — return what we have. Cyclic theories will + // produce an under-augmented body; this is a known limitation. + (TyS::record(row), over_declarations) +} + +/// Flatten a morphism domain/codomain term into the object paths it +/// references. A scalar chain yields one path; a `List` yields one per +/// element. `None` if any leaf isn't a projection chain. +fn term_to_paths(tm: &TmS) -> Option>> { + match &**tm { + TmS_::List(args) => args.iter().flat_map(|a| tms_to_path(a)).collect::>().into(), + _ => Some(vec![tms_to_path(tm)?]), + } +} + +/// Read off a path of `(name, label)` segments from a term that is a chain +/// of projections rooted in a variable. +/// +/// The leading variable is treated as the implicit root (e.g., the `self` +/// of an enclosing record) and contributes no segment. So `Var(self)` +/// returns `[]` and `Proj(Var(self), E, E_label)` returns `[(E, E_label)]`, +/// matching the path representation produced by the surface `.E` syntax. +/// Returns `None` if the term has any other shape. +fn tms_to_path(tm: &TmS) -> Option> { + match &**tm { + // XXX It doesn't know what to do here when the incoming morphism is a multihom + TmS_::Var(_, _, _) => Some(vec![]), + TmS_::Proj(inner, name, label) => { + let mut p = tms_to_path(inner)?; + p.push((*name, *label)); + Some(p) + } + _ => None, + } +} + // NOTE: Most tests for the text elaborator are in the `examples` dir. #[cfg(test)] mod tests { diff --git a/packages/catlog/src/tt/theory.rs b/packages/catlog/src/tt/theory.rs index eda40df80..c17a81431 100644 --- a/packages/catlog/src/tt/theory.rs +++ b/packages/catlog/src/tt/theory.rs @@ -9,7 +9,7 @@ use all_the_same::all_the_same; use derivative::Derivative; -use derive_more::{Constructor, From, TryInto}; +use derive_more::{Constructor, Debug, From, TryInto}; use std::{fmt, rc::Rc}; use super::prelude::*; @@ -26,13 +26,14 @@ use crate::zero::{QualifiedName, name}; /// /// Equality of these theories is nominal; two theories are the same if and only /// if they have the same name. -#[derive(Constructor, Clone, Derivative)] +#[derive(Constructor, Debug, Clone, Derivative)] #[derivative(PartialEq, Eq)] pub struct Theory { /// The name of the theory. pub name: QualifiedName, /// The definition of the theory. #[derivative(PartialEq = "ignore")] + #[debug(skip)] pub definition: TheoryDef, } diff --git a/packages/catlog/src/tt/toplevel.rs b/packages/catlog/src/tt/toplevel.rs index e9f447bcf..d34bfd72b 100644 --- a/packages/catlog/src/tt/toplevel.rs +++ b/packages/catlog/src/tt/toplevel.rs @@ -1,6 +1,7 @@ //! Data structures for managing toplevel declarations in the type theory. //! -//! Specifically, notebooks will produce [TopDecl::Type] declarations. +//! Specifically, notebooks will produce [TopDecl::Type] declarations, or +//! maybe [TopDecl::Diag] declartions. use derive_more::Constructor; @@ -8,7 +9,7 @@ use super::{prelude::*, stx::*, theory::*, val::*}; use crate::zero::QualifiedName; /// A toplevel declaration. -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum TopDecl { /// See [Type]. Type(Type), @@ -16,13 +17,15 @@ pub enum TopDecl { DefConst(DefConst), /// See [Def]. Def(Def), + /// See [Diag]. + Diag(Diag), } /// A toplevel declaration of a type. /// /// Also stores the evaluation of that type. Because this is an evaluation in /// the empty context, this is OK to use in any other context as well. -#[derive(Constructor, Clone)] +#[derive(Constructor, Debug, Clone)] pub struct Type { /// The theory for the type. pub theory: Theory, @@ -37,7 +40,7 @@ pub struct Type { /// Also stores the evaluation of that term, and the evaluation of the /// corresponding type of that term. Because this is an evaluation in the empty /// context, this is OK to use in any other context as well. -#[derive(Constructor, Clone)] +#[derive(Constructor, Debug, Clone)] pub struct DefConst { /// The theory that the constant is defined in. pub theory: Theory, @@ -50,7 +53,7 @@ pub struct DefConst { } /// A toplevel declaration of a term judgment. -#[derive(Constructor, Clone)] +#[derive(Constructor, Debug, Clone)] pub struct Def { /// The theory that the definition is defined in. pub theory: Theory, @@ -64,6 +67,21 @@ pub struct Def { pub body: TmS, } +/// A toplevel declaration of a diagram. +#[derive(Constructor, Debug, Clone)] +pub struct Diag { + /// The theory that the diagram is defined in. + pub theory: Theory, + /// The model G that this diagram is an instance of. + pub model: TyV, + /// The body: a record TyS whose fields have @over-typed entries, + /// presenting both the domain generators and the mapping. + pub body_stx: TyS, + /// Evaluated body — useful for downstream consumers that want the TyV directly. + pub body_val: TyV, + /// Collection of @over declarations and their types. + pub over_decls: Vec<(Vec, (LabelSegment, Vec))>, +} impl TopDecl { /// Unwraps the type for a toplevel-declaration of a type, or panics. /// @@ -94,6 +112,14 @@ impl TopDecl { _ => panic!("top-level should be a term judgment"), } } + + /// Unwraps the diagram for a toplevel diagram declaration, or panics. + pub fn unwrap_diag(self) -> Diag { + match self { + TopDecl::Diag(d) => d, + _ => panic!("top-level should be a diagram declaration"), + } + } } /// Storage for toplevel declarations. diff --git a/packages/catlog/src/tt/util/mod.rs b/packages/catlog/src/tt/util/mod.rs index 4ada5c21e..6074dcd01 100644 --- a/packages/catlog/src/tt/util/mod.rs +++ b/packages/catlog/src/tt/util/mod.rs @@ -6,8 +6,10 @@ pub mod dtry; pub mod idx; pub mod pretty; pub mod row; +pub mod transpiler; pub use dtry::*; pub use idx::*; pub use pretty::*; pub use row::*; +pub use transpiler::*; diff --git a/packages/catlog/src/tt/util/row.rs b/packages/catlog/src/tt/util/row.rs index baf0ada01..7d08d8ed0 100644 --- a/packages/catlog/src/tt/util/row.rs +++ b/packages/catlog/src/tt/util/row.rs @@ -15,7 +15,7 @@ use crate::{tt::prelude::*, zero::LabelSegment}; /// of a row in a database, which is a map from fields to values. /// /// Create this using the [FromIterator] implementation. -#[derive(Clone, Derivative, PartialEq, Eq, From)] +#[derive(Clone, Debug, Derivative, PartialEq, Eq, From)] #[derivative(Default(bound = ""))] pub struct Row(IndexMap); diff --git a/packages/catlog/src/tt/util/transpiler.rs b/packages/catlog/src/tt/util/transpiler.rs new file mode 100644 index 000000000..97309d360 --- /dev/null +++ b/packages/catlog/src/tt/util/transpiler.rs @@ -0,0 +1,256 @@ +use indexmap::{IndexMap, IndexSet}; +use regex::Regex; +use std::collections::HashMap; +use std::sync::LazyLock; + +use crate::tt::eval::Evaluator; +use crate::tt::modelgen::diagram_from_diag; +use crate::tt::stx::{TmS, TmS_, TyS_}; +use crate::tt::toplevel::{TopDecl, Toplevel}; +use crate::tt::val::{TmV, TyV, TyV_}; +use crate::zero::name_seg; + +static ANON: LazyLock = LazyLock::new(|| Regex::new(r"(.+)_(\w+)([0-9]+)$").unwrap()); + +pub trait JuliaTranspiler { + fn transpile(&self) -> String; +} + +pub struct Decapodes { + pub pode: TyV, +} + +impl JuliaTranspiler for Decapodes { + fn transpile(&self) -> String { + let TyV_::Record(_) = &*self.pode else { + panic!() + }; + let toplevel = Toplevel::new(Default::default()); + let eval = Evaluator::empty(&toplevel); + let (self_n, eval) = eval.bind_self(self.pode.clone()); + let self_v = eval.eta_neu(&self_n, &self.pode); + + let mut subs = HashMap::new(); + let mut obs = IndexMap::new(); + let mut mors = IndexSet::new(); + collect_fields(&eval, &self.pode, &self_v, "", &mut obs, &mut mors, &mut subs); + + // Remove specialized obs from declarations + for bound in subs.keys() { + // XXX swap_remove is slow, i understand + obs.swap_remove(bound); + } + + // Rewrite morphism terms + let mors: IndexSet<_> = mors + .into_iter() + .map(|(lhs, rhs)| { + let mut lhs = lhs; + let mut rhs = rhs; + // TODO this is not happy. what if a non-variable term matched the replacement substring + for (from, to) in &subs { + lhs = lhs.replace(from.as_str(), to); + rhs = rhs.replace(from.as_str(), to); + } + (lhs, rhs) + }) + .collect(); + + let mut out = String::new(); + + for (ob, ty) in obs { + // `first` is unhappy + if !ANON.is_match(&format!("{ob}")) { + out.push_str(&format!("\t{}::{}\n", ob, ty.first().unwrap())); + } + } + + for (lhs, rhs) in mors { + out.push_str(&format!("\n\t{} == {}", lhs, rhs)); + } + out + } +} + +fn collect_fields( + eval: &Evaluator, + ty: &TyV, + self_v: &TmV, + prefix: &str, + obs: &mut IndexMap>, + mors: &mut IndexSet<(String, String)>, + subs: &mut HashMap, +) { + let TyV_::Record(r) = &**ty else { return }; + for (name, (label, _)) in r.fields.iter() { + let field_ty = eval.field_ty(ty, self_v, *name); + let field_v = eval.proj(self_v, *name, *label); + let qt = eval.quote_ty(&field_ty); + + let full_label = if prefix.is_empty() { + label.to_string() + } else { + format!("{}_{}", prefix, label) + }; + match &*qt { + TyS_::Over(path) => { + let p = path.iter().map(|(_, l)| l.to_string()).collect(); + if subs.get(&full_label).is_none() { + obs.insert(full_label, p); + } + } + TyS_::Morphism(_, dom, cod) => { + let dom = to_plain_text(dom); + let cod = to_plain_text(cod); + let op = &format!("{label}"); + if EQ.is_match(op) { + if obs.contains_key(&dom) && obs.contains_key(&cod) { + subs.insert(dom, cod); + } else { + mors.insert((cod, dom)); + } + } else { + let op = match op { + op if ADD.is_match(op) => "+", + op if SUBTRACT.is_match(op) => "-", + op if MULT.is_match(op) => "*", + op if PARTIAL.is_match(op) => &format!("{}{}", '\u{2202}', '\u{209C}'), + op if LAPL.is_match(op) => &format!("{}", '\u{0394}'), + op => op, + }; + mors.insert((cod, format!("{op}({dom})"))); + } + } + TyS_::Sing(_, tm) => { + let target = to_plain_text(tm); + subs.insert(full_label, target); + } + TyS_::Record(_) => { + // Recurse into sub-diagram + collect_fields(eval, &field_ty, &field_v, &full_label, obs, mors, subs); + } + // TODO whither the specializations? + TyS_::Specialize(_, _) => { + collect_fields(eval, &field_ty, &field_v, &full_label, obs, mors, subs); + } + _ => {} + } + } +} + +static ADD: LazyLock = LazyLock::new(|| Regex::new(r"add-(.+)$").unwrap()); +static SUBTRACT: LazyLock = LazyLock::new(|| Regex::new(r"subtract-(.+)$").unwrap()); +static MULT: LazyLock = LazyLock::new(|| Regex::new(r"multiplication-(.+)$").unwrap()); +static PARTIAL: LazyLock = LazyLock::new(|| Regex::new(r"partial-(.+)$").unwrap()); +static LAPL: LazyLock = LazyLock::new(|| Regex::new(r"laplace-(.+)$").unwrap()); +static EQ: LazyLock = LazyLock::new(|| Regex::new(r"eq-(.+)$").unwrap()); + +fn to_plain_text(tm: &TmS) -> String { + match &**tm { + TmS_::Var(_, name, _) => { + let s = name.to_string(); + if s == "self" { String::new() } else { s } + } + TmS_::Proj(inner, _, label) => { + let prefix = to_plain_text(inner); + let n = label.to_string(); + if prefix.is_empty() { + n + } else { + format!("{prefix}_{n}") + } + } + TmS_::ObApp(op, args) => { + let op = &format!("{op}"); + let op = match op { + _ if ADD.is_match(op) => "+", + _ if SUBTRACT.is_match(op) => "-", + _ if MULT.is_match(op) => "*", + _ if PARTIAL.is_match(op) => &format!("{}{}", '\u{2202}', '\u{209C}'), + _ if LAPL.is_match(op) => &format!("{}", '\u{0394}'), + _ => op, + }; + format!("{op}({})", to_plain_text(args)) + } + TmS_::List(args) => args.iter().map(|a| to_plain_text(a)).collect::>().join(", "), + _ => format!("{}", tm), + } +} + +// ================================================================================= + +/// Enables transpiling a .dbltt file to a valid Decapode +pub struct TextModel { + pub decl: String, + pub toplevel: Toplevel, +} + +impl JuliaTranspiler for TextModel { + /// Transpiles a .dbltt file, a valid toplevel declaration in it into a valid Julia expression. In this case, we're fixed to Decapodes.jl + fn transpile(&self) -> String { + let diag = match self.toplevel.declarations.get(&name_seg(self.decl.clone())) { + Some(TopDecl::Diag(d)) => d.clone(), + _ => panic!(), + }; + + let Ok((_, _, equations)) = + diagram_from_diag(&self.toplevel, &diag.theory.definition, &diag) + else { + panic!("Error with destructuring diagram") + }; + + // the transpilation result. + let mut out: String = Default::default(); + + let mut tms: HashMap = Default::default(); + // unhappy + let re = regex::Regex::new(r"(.+)_(\w+)([0-9]+)$").unwrap(); + for ob in diag.over_decls { + // I dislike indexing into the tuple's position when i could be indexing into the field of a struct, which would be more informative. + let tm = ob.0.into_iter().map(|n| n.to_string()).collect::>().join("_"); + let ty = ob.1.1.into_iter().map(|n| n.to_string()).collect::>().join("_"); + // remove anonymous variables, e.g., those whose name is specified by the match expression above + if !re.is_match(&tm) { + tms.insert(tm, ty); + } + } + + let mut eqs: HashMap = HashMap::new(); + let mut substitute: HashMap = HashMap::new(); + for (lhs, rhs) in &equations { + let lhs = to_plain_text(lhs); + let rhs = to_plain_text(rhs); + if tms.get(&lhs).is_some() && tms.get(&rhs).is_some() { + substitute.insert(lhs.clone(), rhs.clone()); + } else { + } + eqs.insert(lhs, rhs); + } + + // if there would exists an equality between two declared ojects, e.g., + // ``` + // a::Form0 + // b::Form0 + // a == b + // ``` + // then instead treat the LHS object as an anonymous variable, e.g., + // ``` + // b::Form0 + // a == b + // ``` + for (tm, ty) in tms.iter() { + // if there isn't a substitution (e.g., `a == b`), then add the object as a declaration. + if substitute.get(&tm.clone()).is_none() { + out.push_str(&format!("\t{}::{}\n", tm, ty)) + } + } + + for (lhs, rhs) in eqs.iter() { + out.push_str(&format!("\n\t{} == {}", lhs, rhs)); + } + + // interpolate the diagram into a template expected by the program in the target language, e.g., + // insert `out` into a Decapode macro call. + format!("@decapode begin\n{out}\nend") + } +} diff --git a/packages/catlog/src/tt/val.rs b/packages/catlog/src/tt/val.rs index 3caa62e11..35b8d63dd 100644 --- a/packages/catlog/src/tt/val.rs +++ b/packages/catlog/src/tt/val.rs @@ -3,6 +3,7 @@ //! See [crate::tt] for what this means. use bwd::Bwd; +use derive_more::Debug; use derive_more::Deref; use super::{prelude::*, stx::*, theory::*}; @@ -12,7 +13,7 @@ use crate::zero::{LabelSegment, QualifiedName}; pub type Env = Bwd; /// The content of a record type value. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RecordV { /// The closed-over environment. pub env: Env, @@ -22,6 +23,7 @@ pub struct RecordV { /// /// When we get to actually computing the type of fields, we will look here /// to see if they have been specialized. + #[debug(skip)] pub specializations: Dtry, } @@ -77,6 +79,7 @@ pub fn merge_specializations(old: &Dtry, new: &Dtry) -> Dtry { } /// Inner enum for [TyV]. +#[derive(Debug)] pub enum TyV_ { /// Type constructor for object types, also see [TyS_::Object]. Object(ObType), @@ -98,10 +101,16 @@ pub enum TyV_ { Unit, /// A metavariable, also see [TyS_::Meta]. Meta(MetaVar), + /// The type of terms in a fiber over an object generator of some + /// diagram's codomain model. + /// + /// See [TyS_::Over] for the syntactic counterpart and explanation + /// of why diagram identity is not part of this type. + Over(Vec<(FieldName, LabelSegment)>), } /// Value for total types, dereferences to [TyV_]. -#[derive(Clone, Deref)] +#[derive(Clone, Debug, Deref)] #[deref(forward)] pub struct TyV(Rc); @@ -177,10 +186,15 @@ impl TyV { pub fn meta(mv: MetaVar) -> Self { Self(Rc::new(TyV_::Meta(mv))) } + + /// Smart constructor for [TyV], [TyV_::Over] case. + pub fn over(path: Vec<(FieldName, LabelSegment)>) -> Self { + Self(Rc::new(TyV_::Over(path))) + } } /// Inner enum for [TmN]. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Debug, Eq)] pub enum TmN_ { /// Variable. Var(FwdIdx, VarName, LabelSegment), @@ -189,7 +203,7 @@ pub enum TmN_ { } /// Neutrals for [terms](TmV), dereferences to [TmN_]. -#[derive(Clone, Deref, PartialEq, Eq)] +#[derive(Clone, Debug, Deref, PartialEq, Eq)] #[deref(forward)] pub struct TmN(Rc); @@ -218,6 +232,7 @@ impl TmN { } /// Inner enum for [TmV]. +#[derive(Debug)] pub enum TmV_ { /// Neutrals. /// @@ -242,7 +257,7 @@ pub enum TmV_ { } /// Values for terms, dereferences to [TmV_]. -#[derive(Clone, Deref)] +#[derive(Clone, Debug, Deref)] #[deref(forward)] pub struct TmV(Rc); diff --git a/packages/document-methods/package.json b/packages/document-methods/package.json index 0e9129293..a796f16ca 100644 --- a/packages/document-methods/package.json +++ b/packages/document-methods/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@types/uuid": "^10.0.0", - "typescript": "^5.2.2", - "wasm-pack": "^0.14.0" + "typescript": "^6.0.3", + "wasm-pack": "^0.15.0" } } diff --git a/packages/document-methods/pnpm-lock.yaml b/packages/document-methods/pnpm-lock.yaml index 4caf9daf6..8ed08b845 100644 --- a/packages/document-methods/pnpm-lock.yaml +++ b/packages/document-methods/pnpm-lock.yaml @@ -22,107 +22,42 @@ importers: specifier: ^10.0.0 version: 10.0.0 typescript: - specifier: ^5.2.2 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 wasm-pack: - specifier: ^0.14.0 - version: 0.14.0 + specifier: ^0.15.0 + version: 0.15.0 packages: + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - binary-install@1.1.2: - resolution: {integrity: sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==} - engines: {node: '>=10'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.14: + resolution: {integrity: sha512-/7sHKgQO3JLP9ESlwTYUUftHUadOURUqq23xs1vjcnp8Vss6k0wCfzulyEtk5g91pjvnuriimGlyG7k6msrzRw==} + engines: {node: '>=18'} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -130,117 +65,47 @@ packages: resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} hasBin: true - wasm-pack@0.14.0: - resolution: {integrity: sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==} + wasm-pack@0.15.0: + resolution: {integrity: sha512-DdqtGWc3+iFx+7lL7QU5LBWs7qMnwQSWxF0htSfE15sNa3roVwHjAkTm2JXgueU2GGfSwNqbq2EzyC2b/biKDA==} + engines: {node: '>=16'} hasBin: true - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} snapshots: - '@types/uuid@10.0.0': {} - - axios@0.26.1: - dependencies: - follow-redirects: 1.15.11 - transitivePeerDependencies: - - debug - - balanced-match@1.0.2: {} - - binary-install@1.1.2: - dependencies: - axios: 0.26.1 - rimraf: 3.0.2 - tar: 6.2.1 - transitivePeerDependencies: - - debug - - brace-expansion@1.1.14: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - chownr@2.0.0: {} - - concat-map@0.0.1: {} - - follow-redirects@1.15.11: {} - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - - fs.realpath@1.0.0: {} - - glob@7.2.3: + '@isaacs/fs-minipass@4.0.1': dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.14 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 + minipass: 7.1.3 - minipass@5.0.0: {} - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} + '@types/uuid@10.0.0': {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 + chownr@3.0.0: {} - path-is-absolute@1.0.1: {} + minipass@7.1.3: {} - rimraf@3.0.2: + minizlib@3.1.0: dependencies: - glob: 7.2.3 + minipass: 7.1.3 - tar@6.2.1: + tar@7.5.14: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 tiny-invariant@1.3.3: {} - typescript@5.9.3: {} + typescript@6.0.3: {} uuid@13.0.0: {} - wasm-pack@0.14.0: + wasm-pack@0.15.0: dependencies: - binary-install: 1.1.2 - transitivePeerDependencies: - - debug - - wrappy@1.0.2: {} + tar: 7.5.14 - yallist@4.0.0: {} + yallist@5.0.0: {} diff --git a/packages/document-types/src/v0/diagram_judgment.rs b/packages/document-types/src/v0/diagram_judgment.rs index dc15d04f9..616008af2 100644 --- a/packages/document-types/src/v0/diagram_judgment.rs +++ b/packages/document-types/src/v0/diagram_judgment.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use tsify::Tsify; use uuid::Uuid; +use crate::v1::Link; + use super::model::{Mor, Ob}; use super::theory::{MorType, ObType}; @@ -80,4 +82,36 @@ pub enum DiagramJudgment { /// Declares an equation between morphisms in the diagram. #[serde(rename = "equation")] Equation(DiagramEqnDecl), + + /// + #[serde(rename = "instantiation")] + Instantiation(InstantiatedDiagram), +} + +/// Instantiates an existing diagram into the current diagram +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub struct InstantiatedDiagram { + /// Human-readable label for the instantiation. + pub name: String, + + /// Globally unique identifier of the instantiation. + pub id: Uuid, + + /// Link to the diagram to instantiate. + pub diagram: Option, + + /// List of specializations to perform on the instantiated diagram. + pub specializations: Vec, +} + +/// A specialization of a generating object in an instantiated diagram. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub struct SpecializeDiagram { + /// ID (qualified name) of generating object to specialize. + pub id: Option, + + /// Object to insert as the specialization. + pub ob: Option, } diff --git a/packages/frontend/README.md b/packages/frontend/README.md index 1403ad7bd..16dd48bf3 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -31,16 +31,3 @@ where `$MODE` is replaced with one of the following: Running the command above builds the Wasm and other local dependencies (by running `pnpm run build:deps`) before launching the Vite preview server. - -## Troubleshooting - -### Nix Hash Mismatches - -If this package fails to build in Nix with the error: - -``` -> ERROR: pnpm failed to install dependencies -``` - -Refer to the "pnpm Dependencies" section in [Fixing Hash Mismatches in -Nix](../../dev-docs/fixing-hash-mismatches.md). diff --git a/packages/frontend/default.nix b/packages/frontend/default.nix index 52186de96..21d0fafaa 100644 --- a/packages/frontend/default.nix +++ b/packages/frontend/default.nix @@ -2,6 +2,7 @@ pkgs, self, lib, + pnpm2nix, ... }: let @@ -9,8 +10,60 @@ let name = packageJson.name; version = packageJson.version; + # pnpm2nix-nzbr exposes a `processLockfile` function in lockfile.nix that, given a + # pnpm-lock.yaml, produces: + # - dependencyTarballs: a list of FODs (fetchurl/fetchGit/...) for each + # dependency tarball referenced by the lockfile. + # - patchedLockfile: an in-memory copy of the lockfile with every + # `resolution.tarball` rewritten to point at the local nix store tarball. + # + # We use these to drive an offline `pnpm install --frozen-lockfile`, which + # avoids having to maintain a monolithic `pnpmDeps.hash` by hand: every + # individual tarball is hashed via the integrity field already present in + # the lockfile. + lockfileLib = pkgs.callPackage (pnpm2nix + "/lockfile.nix") { }; + + processLock = lockfile: lockfileLib.processLockfile { + registry = "https://registry.npmjs.org"; + noDevDependencies = false; + inherit lockfile; + }; + + # The frontend package and every sibling workspace member it pulls in via + # `link:` need their lockfiles processed: each lockfile is rewritten to + # reference nix-store tarballs, and the union of all dependency tarballs + # is fed to `pnpm store add` so the offline install can resolve everything. + # Because `.npmrc` sets `shared-workspace-lockfile=false`, each workspace + # member has its own pnpm-lock.yaml that must be processed independently. + lockfilesToProcess = { + "packages/frontend" = ../frontend/pnpm-lock.yaml; + "packages/ui-components" = ../ui-components/pnpm-lock.yaml; + "packages/document-methods" = ../document-methods/pnpm-lock.yaml; + "packages/backend/pkg" = ../backend/pkg/pnpm-lock.yaml; + }; + + processedLocks = lib.mapAttrs (_: processLock) lockfilesToProcess; + + yamlFormat = pkgs.formats.yaml { }; + + patchedLockfiles = lib.mapAttrs ( + relPath: processed: + yamlFormat.generate "pnpm-lock-${builtins.replaceStrings [ "/" ] [ "-" ] relPath}.yaml" + processed.patchedLockfile + ) processedLocks; + + allTarballs = lib.unique ( + lib.concatLists (lib.mapAttrsToList (_: p: p.dependencyTarballs) processedLocks) + ); + + # File containing the space-separated list of all dependency tarball paths, + # consumed by `pnpm store add` to seed the offline store. + dependencyTarballsFile = pkgs.runCommand "${name}-dependency-tarballs" { } '' + echo ${lib.concatStringsSep " " allTarballs} > $out + ''; + commonAttrs = { - version = version; + inherit version; # Filter source to only include packages needed for frontend build # This prevents unnecessary rebuilds when unrelated files change src = lib.fileset.toSource { @@ -19,6 +72,7 @@ let ../../.npmrc ../../pnpm-workspace.yaml ../../pnpm-lock.yaml + (lib.fileset.maybeMissing ../../patches) ../../packages/frontend ../../packages/ui-components ../../packages/document-methods @@ -27,38 +81,48 @@ let }; nativeBuildInputs = with pkgs; [ - pnpm.configHook + nodejs_24 + pnpm ]; buildInputs = with pkgs; [ nodejs_24 ]; - pnpmDeps = pkgs.fetchPnpmDeps { - # see ../../dev-docs/fixing-hash-mismatches.md - hash = "sha256-n/0a/wK2mLkGvKWSBs/8zdfgF+dxMmPz3Ir1k30Qw9s="; - - pname = name; - fetcherVersion = 2; - # Only includes package.json and pnpm-lock.yaml files to ensure consistent hashing in different - # environments - src = lib.fileset.toSource { - root = ../../.; - fileset = lib.fileset.unions [ - ../../.npmrc - ../../pnpm-workspace.yaml - ../../pnpm-lock.yaml - ../../packages/frontend/package.json - ../../packages/frontend/pnpm-lock.yaml - ../../packages/ui-components/package.json - ../../packages/ui-components/pnpm-lock.yaml - ../../packages/document-methods/package.json - ../../packages/document-methods/pnpm-lock.yaml - ../../packages/backend/pkg/package.json - ../../packages/backend/pkg/pnpm-lock.yaml - ]; - }; - }; + # Drive pnpm install ourselves, using the lockfile-derived nix store + # tarballs from pnpm2nix-nzbr instead of pnpm.configHook + fetchPnpmDeps. + # This phase runs from the unpacked source root (monorepo subset). + configurePhase = '' + runHook preConfigure + + export HOME=$NIX_BUILD_TOP + export npm_config_nodedir=${pkgs.nodejs_24} + + # Replace each workspace member's lockfile with the version whose tarball + # references point at /nix/store paths so pnpm doesn't hit the network. + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (relPath: patched: '' + cp -fv ${patched} ${relPath}/pnpm-lock.yaml + '') patchedLockfiles + )} + + # Pre-populate the local pnpm content-addressable store with every + # tarball referenced by the patched lockfiles. + store=$(pnpm store path) + mkdir -p "$(dirname "$store")" + pnpm store add $(cat ${dependencyTarballsFile}) + + # Install dependencies for every workspace member. Recursive install + # handles the `link:` deps between members correctly. + pnpm install \ + --recursive \ + --ignore-scripts \ + --force \ + --frozen-lockfile \ + --prefer-offline + + runHook postConfigure + ''; }; package = pkgs.stdenv.mkDerivation ( @@ -67,29 +131,37 @@ let pname = name; buildPhase = '' + runHook preBuild + # Set up catlog-wasm before TypeScript build needs it mkdir -p packages/catlog-wasm/dist/pkg-browser - cp -r ${self.packages.x86_64-linux.catlog-wasm-browser}/* packages/catlog-wasm/dist/pkg-browser/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.catlog-wasm-browser}/* packages/catlog-wasm/dist/pkg-browser/ # Set up document-types wasm output mkdir -p packages/document-types/pkg - cp -r ${self.packages.x86_64-linux.document-types-wasm}/* packages/document-types/pkg/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.document-types-wasm}/* packages/document-types/pkg/ # Set up generated API bindings mkdir -p packages/backend/pkg/src - cp -r ${self.packages.x86_64-linux.catcolabApi}/src packages/backend/pkg/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.catcolabApi}/src packages/backend/pkg/ cd packages/frontend # Generate CSS module type declarations - npm run build:tcm + pnpm run build:tcm # Build with development mode to use .env.development configuration - npm run build -- --mode development + pnpm run build -- --mode development cd - + + runHook postBuild ''; installPhase = '' + runHook preInstall + mkdir -p $out cp -r packages/frontend/dist/* $out + + runHook postInstall ''; } ); @@ -107,21 +179,26 @@ let dontStrip = true; dontPatchShebangs = true; + # No build step; we just package up the tree with node_modules. + dontBuild = true; + installPhase = '' + runHook preInstall + # for vitest to work we need to basically recreate the development environment. We achieve this # by setting of up a copy of the packages structure. mkdir -p $out/packages mkdir -p $out/packages/catlog-wasm/dist/pkg-browser - cp -r ${self.packages.x86_64-linux.catlog-wasm-browser}/* $out/packages/catlog-wasm/dist/pkg-browser/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.catlog-wasm-browser}/* $out/packages/catlog-wasm/dist/pkg-browser/ # Set up document-types wasm output before copying to $out mkdir -p packages/document-types/pkg - cp -r ${self.packages.x86_64-linux.document-types-wasm}/* packages/document-types/pkg/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.document-types-wasm}/* packages/document-types/pkg/ # Bindings must be copied into source tree BEFORE the cp below copies backend to $out mkdir -p packages/backend/pkg/src - cp -r ${self.packages.x86_64-linux.catcolabApi}/src packages/backend/pkg/ + cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.catcolabApi}/src packages/backend/pkg/ cp -r packages/backend $out/packages/ cp -r packages/frontend $out/packages/ @@ -181,6 +258,8 @@ let # Wrap the script to ensure nodejs is in PATH makeWrapper $out/bin/.${name}-tests-unwrapped $out/bin/${name}-tests \ --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.nodejs_24 ]} + + runHook postInstall ''; meta.mainProgram = "${name}-tests"; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index d9b441644..c22d2ff4e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -52,8 +52,8 @@ "catcolab-document-types": "link:../document-types/pkg", "catcolab-ui-components": "link:../ui-components", "catlog-wasm": "link:../catlog-wasm/dist/pkg-browser", - "echarts": "^5.5.1", - "echarts-solid": "^0.2.0", + "echarts": "^6.1.0", + "echarts-solid": "github:ToposInstitute/echarts-solid#kb/echarts-v6-dist", "elkjs": "^0.11.0", "fast-equals": "^5.2.2", "fast-json-patch": "^3.1.1", @@ -88,12 +88,12 @@ "solid-mdx": "^0.0.7", "typed-css-modules": "^0.9.1", "typedoc": "^0.26.8", - "typescript": "^5.2.2", + "typescript": "^6.0.3", "vite": "^7.2.2", "vite-plugin-solid": "^2.11.10", "vite-plugin-wasm": "^3.5.0", "vitest": "^4.0.8", - "wasm-pack": "^0.14.0", + "wasm-pack": "^0.15.0", "web-worker": "^1.5.0" } } diff --git a/packages/frontend/pnpm-lock.yaml b/packages/frontend/pnpm-lock.yaml index 562181a83..af36bf556 100644 --- a/packages/frontend/pnpm-lock.yaml +++ b/packages/frontend/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: version: 3.1.0(acorn@8.16.0)(rollup@4.53.2) '@modular-forms/solid': specifier: ^0.24.1 - version: 0.24.1(solid-js@1.9.10)(typescript@5.6.2) + version: 0.24.1(solid-js@1.9.10)(typescript@6.0.3) '@qubit-rs/client': specifier: ^0.4.5 version: 0.4.5 @@ -93,11 +93,11 @@ importers: specifier: link:../catlog-wasm/dist/pkg-browser version: link:../catlog-wasm/dist/pkg-browser echarts: - specifier: ^5.5.1 - version: 5.5.1 + specifier: ^6.1.0 + version: 6.1.0 echarts-solid: - specifier: ^0.2.0 - version: 0.2.0(echarts@5.5.1)(solid-js@1.9.10) + specifier: github:ToposInstitute/echarts-solid#kb/echarts-v6-dist + version: https://codeload.github.com/ToposInstitute/echarts-solid/tar.gz/fa9fd59f51067c3ad3f6aaddb6c62eadad738f8a(echarts@6.1.0)(solid-js@1.9.10) elkjs: specifier: ^0.11.0 version: 0.11.0 @@ -176,7 +176,7 @@ importers: version: 24.10.0 eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.39.4)(typescript@5.6.2) + version: 0.14.5(eslint@9.39.4)(typescript@6.0.3) rehype-katex: specifier: ^7.0.1 version: 7.0.1 @@ -194,10 +194,10 @@ importers: version: 0.9.1 typedoc: specifier: ^0.26.8 - version: 0.26.8(typescript@5.6.2) + version: 0.26.8(typescript@6.0.3) typescript: - specifier: ^5.2.2 - version: 5.6.2 + specifier: ^6.0.3 + version: 6.0.3 vite: specifier: ^7.2.2 version: 7.2.2(@types/node@24.10.0)(yaml@2.5.1) @@ -211,8 +211,8 @@ importers: specifier: ^4.0.8 version: 4.0.8(@types/debug@4.1.12)(@types/node@24.10.0)(yaml@2.5.1) wasm-pack: - specifier: ^0.14.0 - version: 0.14.0 + specifier: ^0.15.0 + version: 0.15.0 web-worker: specifier: ^1.5.0 version: 1.5.0 @@ -821,12 +821,16 @@ packages: engines: {node: '>=6'} hasBin: true - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -841,6 +845,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1086,28 +1094,33 @@ packages: peerDependencies: solid-js: ^1.6.12 + '@solid-primitives/event-listener@2.4.5': + resolution: {integrity: sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA==} + peerDependencies: + solid-js: ^1.6.12 + '@solid-primitives/map@0.7.2': resolution: {integrity: sha512-sXK/rS68B4oq3XXNyLrzVhLtT1pnimmMUahd2FqhtYUuyQsCfnW058ptO1s+lWc2k8F/3zQSNVkZ2ifJjlcNbQ==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/refs@1.0.8': - resolution: {integrity: sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==} + '@solid-primitives/refs@1.1.3': + resolution: {integrity: sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/resize-observer@2.0.26': - resolution: {integrity: sha512-KbPhwal6ML9OHeUTZszBbt6PYSMj89d4wVCLxlvDYL4U0+p+xlCEaqz6v9dkCwm/0Lb+Wed7W5T1dQZCP3JUUw==} + '@solid-primitives/resize-observer@2.1.5': + resolution: {integrity: sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/rootless@1.4.5': - resolution: {integrity: sha512-GFJE9GC3ojx0aUKqAUZmQPyU8fOVMtnVNrkdk2yS4kd17WqVSpXpoTmo9CnOwA+PG7FTzdIkogvfLQSLs4lrww==} + '@solid-primitives/rootless@1.5.3': + resolution: {integrity: sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/static-store@0.0.8': - resolution: {integrity: sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg==} + '@solid-primitives/static-store@0.1.3': + resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} peerDependencies: solid-js: ^1.6.12 @@ -1121,13 +1134,13 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/utils@6.2.3': - resolution: {integrity: sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==} + '@solid-primitives/utils@6.3.2': + resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/utils@6.3.2': - resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} + '@solid-primitives/utils@6.4.0': + resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} peerDependencies: solid-js: ^1.6.12 @@ -1177,6 +1190,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} @@ -1300,8 +1316,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1338,9 +1354,6 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - babel-plugin-jsx-dom-expressions@0.39.2: resolution: {integrity: sha512-rCkSYFuLl5/XD+BXjZk1XxFAsIBgNe9WZ7xBHjQV1dBliI64kO+EWktAD3b6Bj/SXk+LpVXFyMVydhnI35svWQ==} peerDependencies: @@ -1368,16 +1381,11 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - binary-install@1.1.2: - resolution: {integrity: sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==} - engines: {node: '>=10'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - bind-event-listener@3.0.0: resolution: {integrity: sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1450,9 +1458,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -1540,15 +1548,16 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - echarts-solid@0.2.0: - resolution: {integrity: sha512-ilGFJ1zOUza4asiBoNu47TQYtL1WlUwp/BL9TGoDwAXbZczyqUl5RaDEQHRHTZiMh/4t0lenXw0QHRnGToOYkg==} + echarts-solid@https://codeload.github.com/ToposInstitute/echarts-solid/tar.gz/fa9fd59f51067c3ad3f6aaddb6c62eadad738f8a: + resolution: {tarball: https://codeload.github.com/ToposInstitute/echarts-solid/tar.gz/fa9fd59f51067c3ad3f6aaddb6c62eadad738f8a} + version: 0.2.0 engines: {node: '>=18', pnpm: '>=8.6.0'} peerDependencies: - echarts: ^5.5.1 + echarts: ^6.1.0 solid-js: ^1.8.22 - echarts@5.5.1: - resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==} + echarts@6.1.0: + resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} electron-to-chromium@1.5.33: resolution: {integrity: sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==} @@ -1745,29 +1754,13 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.1: - resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} - - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1793,10 +1786,6 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -1904,13 +1893,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -2330,9 +2312,6 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -2340,26 +2319,13 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} @@ -2397,9 +2363,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - oniguruma-to-js@0.4.3: resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} @@ -2438,10 +2401,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2638,11 +2597,6 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.53.2: resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2792,10 +2746,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.14: + resolution: {integrity: sha512-/7sHKgQO3JLP9ESlwTYUUftHUadOURUqq23xs1vjcnp8Vss6k0wCfzulyEtk5g91pjvnuriimGlyG7k6msrzRw==} + engines: {node: '>=18'} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2859,8 +2812,8 @@ packages: peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -3079,8 +3032,9 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - wasm-pack@0.14.0: - resolution: {integrity: sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==} + wasm-pack@0.15.0: + resolution: {integrity: sha512-DdqtGWc3+iFx+7lL7QU5LBWs7qMnwQSWxF0htSfE15sNa3roVwHjAkTm2JXgueU2GGfSwNqbq2EzyC2b/biKDA==} + engines: {node: '>=16'} hasBin: true web-namespaces@2.0.1: @@ -3119,9 +3073,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -3144,8 +3095,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} yaml@2.5.1: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} @@ -3164,8 +3116,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zrender@5.6.0: - resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==} + zrender@6.1.0: + resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3534,7 +3486,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.14.0 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 @@ -3911,13 +3863,18 @@ snapshots: protobufjs: 7.4.0 yargs: 17.7.2 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -3931,6 +3888,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -4017,10 +3978,10 @@ snapshots: - acorn - supports-color - '@modular-forms/solid@0.24.1(solid-js@1.9.10)(typescript@5.6.2)': + '@modular-forms/solid@0.24.1(solid-js@1.9.10)(typescript@6.0.3)': dependencies: solid-js: 1.9.10 - valibot: 1.0.0-beta.0(typescript@5.6.2) + valibot: 1.0.0-beta.0(typescript@6.0.3) transitivePeerDependencies: - typescript @@ -4171,32 +4132,37 @@ snapshots: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/event-listener@2.4.5(solid-js@1.9.10)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/map@0.7.2(solid-js@1.9.10)': dependencies: '@solid-primitives/trigger': 1.2.2(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/refs@1.0.8(solid-js@1.9.10)': + '@solid-primitives/refs@1.1.3(solid-js@1.9.10)': dependencies: - '@solid-primitives/utils': 6.2.3(solid-js@1.9.10) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/resize-observer@2.0.26(solid-js@1.9.10)': + '@solid-primitives/resize-observer@2.1.5(solid-js@1.9.10)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/rootless': 1.4.5(solid-js@1.9.10) - '@solid-primitives/static-store': 0.0.8(solid-js@1.9.10) - '@solid-primitives/utils': 6.2.3(solid-js@1.9.10) + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.10) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.10) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.10) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/rootless@1.4.5(solid-js@1.9.10)': + '@solid-primitives/rootless@1.5.3(solid-js@1.9.10)': dependencies: - '@solid-primitives/utils': 6.2.3(solid-js@1.9.10) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/static-store@0.0.8(solid-js@1.9.10)': + '@solid-primitives/static-store@0.1.3(solid-js@1.9.10)': dependencies: - '@solid-primitives/utils': 6.2.3(solid-js@1.9.10) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) solid-js: 1.9.10 '@solid-primitives/timer@1.4.2(solid-js@1.9.10)': @@ -4205,14 +4171,14 @@ snapshots: '@solid-primitives/trigger@1.2.2(solid-js@1.9.10)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/utils@6.2.3(solid-js@1.9.10)': + '@solid-primitives/utils@6.3.2(solid-js@1.9.10)': dependencies: solid-js: 1.9.10 - '@solid-primitives/utils@6.3.2(solid-js@1.9.10)': + '@solid-primitives/utils@6.4.0(solid-js@1.9.10)': dependencies: solid-js: 1.9.10 @@ -4269,6 +4235,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.11 @@ -4303,12 +4271,12 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/project-service@8.57.0(typescript@5.6.2)': + '@typescript-eslint/project-service@8.57.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.6.2) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@6.0.3) '@typescript-eslint/types': 8.57.0 debug: 4.4.3 - typescript: 5.6.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4317,35 +4285,35 @@ snapshots: '@typescript-eslint/types': 8.57.0 '@typescript-eslint/visitor-keys': 8.57.0 - '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.6.2)': + '@typescript-eslint/tsconfig-utils@8.57.0(typescript@6.0.3)': dependencies: - typescript: 5.6.2 + typescript: 6.0.3 '@typescript-eslint/types@8.57.0': {} - '@typescript-eslint/typescript-estree@8.57.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.57.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.57.0(typescript@5.6.2) - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.6.2) + '@typescript-eslint/project-service': 8.57.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@6.0.3) '@typescript-eslint/types': 8.57.0 '@typescript-eslint/visitor-keys': 8.57.0 debug: 4.4.3 minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.6.2) - typescript: 5.6.2 + ts-api-utils: 2.4.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.0(eslint@9.39.4)(typescript@5.6.2)': + '@typescript-eslint/utils@8.57.0(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@typescript-eslint/scope-manager': 8.57.0 '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.57.0(typescript@6.0.3) eslint: 9.39.4 - typescript: 5.6.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4409,7 +4377,7 @@ snapshots: acorn@8.16.0: {} - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -4441,12 +4409,6 @@ snapshots: astring@1.9.0: {} - axios@0.26.1: - dependencies: - follow-redirects: 1.15.9 - transitivePeerDependencies: - - debug - babel-plugin-jsx-dom-expressions@0.39.2(@babel/core@7.25.7): dependencies: '@babel/core': 7.25.7 @@ -4473,17 +4435,9 @@ snapshots: binary-extensions@2.3.0: {} - binary-install@1.1.2: - dependencies: - axios: 0.26.1 - rimraf: 3.0.2 - tar: 6.2.1 - transitivePeerDependencies: - - debug - bind-event-listener@3.0.0: {} - brace-expansion@1.1.11: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -4573,7 +4527,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@2.0.0: {} + chownr@3.0.0: {} cliui@8.0.1: dependencies: @@ -4644,17 +4598,17 @@ snapshots: eastasianwidth@0.2.0: {} - echarts-solid@0.2.0(echarts@5.5.1)(solid-js@1.9.10): + echarts-solid@https://codeload.github.com/ToposInstitute/echarts-solid/tar.gz/fa9fd59f51067c3ad3f6aaddb6c62eadad738f8a(echarts@6.1.0)(solid-js@1.9.10): dependencies: - '@solid-primitives/refs': 1.0.8(solid-js@1.9.10) - '@solid-primitives/resize-observer': 2.0.26(solid-js@1.9.10) - echarts: 5.5.1 + '@solid-primitives/refs': 1.1.3(solid-js@1.9.10) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.10) + echarts: 6.1.0 solid-js: 1.9.10 - echarts@5.5.1: + echarts@6.1.0: dependencies: tslib: 2.3.0 - zrender: 5.6.0 + zrender: 6.1.0 electron-to-chromium@1.5.33: {} @@ -4717,16 +4671,16 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@5.6.2): + eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.57.0(eslint@9.39.4)(typescript@5.6.2) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4)(typescript@6.0.3) eslint: 9.39.4 estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 known-css-properties: 0.30.0 style-to-object: 1.0.9 - typescript: 5.6.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4751,11 +4705,11 @@ snapshots: '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 + '@types/estree': 1.0.9 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -4930,24 +4884,16 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.1 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.4.1: {} - - follow-redirects@1.15.9: {} + flatted@3.4.2: {} foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -4972,15 +4918,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globals@11.12.0: {} globals@14.0.0: {} @@ -5187,13 +5124,6 @@ snapshots: imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - inline-style-parser@0.1.1: {} inline-style-parser@0.2.4: {} @@ -5986,32 +5916,19 @@ snapshots: dependencies: brace-expansion: 5.0.4 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - minimatch@3.1.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.14 minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: + minizlib@3.1.0: dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} + minipass: 7.1.2 mkdirp@3.0.1: {} @@ -6034,10 +5951,6 @@ snapshots: normalize-path@3.0.0: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - oniguruma-to-js@0.4.3: dependencies: regex: 4.3.2 @@ -6086,8 +5999,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-scurry@1.11.1: @@ -6366,10 +6277,6 @@ snapshots: resolve-from@4.0.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.53.2: dependencies: '@types/estree': 1.0.8 @@ -6537,14 +6444,13 @@ snapshots: dependencies: has-flag: 4.0.0 - tar@6.2.1: + tar@7.5.14: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 tiny-invariant@1.3.3: {} @@ -6569,9 +6475,9 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.4.0(typescript@5.6.2): + ts-api-utils@2.4.0(typescript@6.0.3): dependencies: - typescript: 5.6.2 + typescript: 6.0.3 ts-pattern@5.4.0: {} @@ -6599,16 +6505,16 @@ snapshots: postcss-modules-values: 4.0.0(postcss@8.4.47) yargs: 17.7.2 - typedoc@0.26.8(typescript@5.6.2): + typedoc@0.26.8(typescript@6.0.3): dependencies: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 shiki: 1.21.0 - typescript: 5.6.2 + typescript: 6.0.3 yaml: 2.5.1 - typescript@5.6.2: {} + typescript@6.0.3: {} uc.micro@2.1.0: {} @@ -6730,9 +6636,9 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 - valibot@1.0.0-beta.0(typescript@5.6.2): + valibot@1.0.0-beta.0(typescript@6.0.3): optionalDependencies: - typescript: 5.6.2 + typescript: 6.0.3 validate-html-nesting@1.2.2: {} @@ -6838,11 +6744,9 @@ snapshots: w3c-keyname@2.2.8: {} - wasm-pack@0.14.0: + wasm-pack@0.15.0: dependencies: - binary-install: 1.1.2 - transitivePeerDependencies: - - debug + tar: 7.5.14 web-namespaces@2.0.1: {} @@ -6879,8 +6783,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrappy@1.0.2: {} - ws@8.18.3: {} xstate@5.20.1: {} @@ -6889,7 +6791,7 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: {} + yallist@5.0.0: {} yaml@2.5.1: {} @@ -6907,7 +6809,7 @@ snapshots: yocto-queue@0.1.0: {} - zrender@5.6.0: + zrender@6.1.0: dependencies: tslib: 2.3.0 diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 4c93e3437..df312edd4 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -5,7 +5,7 @@ import { Navigate, type RouteDefinition, Router, type RouteSectionProps } from " import { type FirebaseOptions, initializeApp } from "firebase/app"; import { getAuth, signOut } from "firebase/auth"; import { FirebaseProvider } from "solid-firebase"; -import { createResource, createSignal, ErrorBoundary, lazy, Show } from "solid-js"; +import { createResource, createSignal, ErrorBoundary, lazy, onMount, Show } from "solid-js"; import invariant from "tiny-invariant"; import * as uuid from "uuid"; @@ -31,20 +31,15 @@ const Root = (props: RouteSectionProps) => { const firebaseApp = initializeApp(firebaseOptions); const api = new Api({ serverUrl, repoUrl, firebaseApp }); - const [isSessionInvalid] = createResource( - // oxlint-disable-next-line solid/reactivity -- createResource fetcher - async () => { - const result = await api.rpc.validate_session.query(); - if (result.tag === "Err") { - await signOut(getAuth(firebaseApp)); - return true; - } - return false; - }, - { - initialValue: false, - }, - ); + const [isSessionInvalid, setIsSessionInvalid] = createSignal(false); + + onMount(async () => { + const result = await api.rpc.validate_session.query(); + if (result.tag === "Err") { + await signOut(getAuth(firebaseApp)); + setIsSessionInvalid(true); + } + }); const theories = stdTheories; const models = createModelLibraryWithApi(api, theories); diff --git a/packages/frontend/src/analysis/analysis_editor.tsx b/packages/frontend/src/analysis/analysis_editor.tsx index bbd48238c..74888b8ba 100644 --- a/packages/frontend/src/analysis/analysis_editor.tsx +++ b/packages/frontend/src/analysis/analysis_editor.tsx @@ -3,6 +3,7 @@ import { Dynamic } from "solid-js/web"; import invariant from "tiny-invariant"; import { Nb } from "catcolab-document-methods"; +import { type FocusHandle } from "catcolab-ui-components"; import { type CellConstructor, type FormalCellEditorProps, NotebookEditor } from "../notebook"; import type { AnalysisMeta, DiagramAnalysisMeta, ModelAnalysisMeta } from "../theory"; import { LiveAnalysisContext } from "./context"; @@ -16,7 +17,10 @@ import type { Analysis } from "./types"; /** Notebook editor for analyses of models of double theories. */ -export function AnalysisNotebookEditor(props: { liveAnalysis: LiveAnalysisDoc }) { +export function AnalysisNotebookEditor(props: { + liveAnalysis: LiveAnalysisDoc; + focus: FocusHandle; +}) { const liveDoc = () => props.liveAnalysis.liveDoc; const cellConstructors = () => { @@ -39,7 +43,7 @@ export function AnalysisNotebookEditor(props: { liveAnalysis: LiveAnalysisDoc }) changeNotebook={(f) => liveDoc().changeDoc((doc) => f(doc.notebook))} formalCellEditor={AnalysisCellEditor} cellConstructors={cellConstructors()} - noShortcuts={true} + focus={props.focus} /> ); diff --git a/packages/frontend/src/components/document_picker.tsx b/packages/frontend/src/components/document_picker.tsx index d049e07cb..ad976b452 100644 --- a/packages/frontend/src/components/document_picker.tsx +++ b/packages/frontend/src/components/document_picker.tsx @@ -55,6 +55,7 @@ export function DocumentPicker( "docType", "placeholder", "isActive", + "focus", "filterCompletions", ]); @@ -68,10 +69,13 @@ export function DocumentPicker( ); const [editMode, setEditMode] = createSignal(false); - const enableEditMode = () => setEditMode(true); + const enableEditMode = () => { + props.focus?.setFocused(true); + setEditMode(true); + }; const disableEditMode = () => setEditMode(false); - createEffect(() => setEditMode(props.isActive ?? false)); + createEffect(() => setEditMode(props.focus?.hasFocus() ?? props.isActive ?? false)); const DocLink = (linkProps: ComponentProps<"a">) => ( @@ -123,6 +127,7 @@ export function DocumentPicker( }> { props.setRefId(refId); diff --git a/packages/frontend/src/diagram/diagram_editor.tsx b/packages/frontend/src/diagram/diagram_editor.tsx index e4ded5756..12e3e93ab 100644 --- a/packages/frontend/src/diagram/diagram_editor.tsx +++ b/packages/frontend/src/diagram/diagram_editor.tsx @@ -4,6 +4,7 @@ import invariant from "tiny-invariant"; import { Diagram, Nb } from "catcolab-document-methods"; import type { DiagramJudgment, DiagramMorDecl, DiagramObDecl } from "catcolab-document-types"; +import { type FocusHandle } from "catcolab-ui-components"; import { LiveModelContext } from "../model"; import { type CellConstructor, type FormalCellEditorProps, NotebookEditor } from "../notebook"; import type { InstanceTypeMeta } from "../theory"; @@ -14,7 +15,7 @@ import { DiagramObjectCellEditor } from "./object_cell_editor"; /** Notebook editor for a diagram in a model. */ -export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc }) { +export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc; focus: FocusHandle }) { const liveDoc = () => props.liveDiagram.liveDoc; const liveModel = () => props.liveDiagram.liveModel; @@ -39,6 +40,7 @@ export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc }) { cellConstructors={cellConstructors()} cellLabel={judgmentLabel} duplicateCell={Diagram.duplicateDiagramJudgment} + focus={props.focus} /> ); @@ -59,7 +61,7 @@ function DiagramCellEditor(props: FormalCellEditorProps) { modifyDecl={(f) => props.changeContent((content) => f(content as DiagramObDecl)) } - isActive={props.isActive} + focus={props.focus} actions={props.actions} theory={theory()} /> @@ -72,7 +74,7 @@ function DiagramCellEditor(props: FormalCellEditorProps) { modifyDecl={(f) => props.changeContent((content) => f(content as DiagramMorDecl)) } - isActive={props.isActive} + focus={props.focus} actions={props.actions} theory={theory()} /> diff --git a/packages/frontend/src/diagram/morphism_cell_editor.tsx b/packages/frontend/src/diagram/morphism_cell_editor.tsx index aafcf5606..bd698c0d3 100644 --- a/packages/frontend/src/diagram/morphism_cell_editor.tsx +++ b/packages/frontend/src/diagram/morphism_cell_editor.tsx @@ -1,7 +1,8 @@ -import { createSignal, useContext } from "solid-js"; +import { useContext } from "solid-js"; import invariant from "tiny-invariant"; import { v7 } from "uuid"; +import { type FocusHandle, useChildFocus } from "catcolab-ui-components"; import type { DiagramMorDecl } from "catlog-wasm"; import { BasicMorInput } from "../model/morphism_input"; import type { CellActions } from "../notebook"; @@ -16,14 +17,15 @@ import "./morphism_cell_editor.css"; export function DiagramMorphismCellEditor(props: { decl: DiagramMorDecl; modifyDecl: (f: (decl: DiagramMorDecl) => void) => void; - isActive: boolean; + focus: FocusHandle; actions: CellActions; theory: Theory; }) { const liveDiagram = useContext(LiveDiagramContext); invariant(liveDiagram, "Live diagram should be provided as context"); - const [activeInput, setActiveInput] = createSignal("mor"); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell. + const focus = useChildFocus(props.focus, { default: "mor" }); const domType = () => props.theory.theory.src(props.decl.morType); const codType = () => props.theory.theory.tgt(props.decl.morType); @@ -54,15 +56,11 @@ export function DiagramMorphismCellEditor(props: { obType={domType()} generateId={v7} isInvalid={domInvalid()} - isActive={props.isActive && activeInput() === "dom"} - deleteForward={() => setActiveInput("mor")} - exitBackward={() => setActiveInput("mor")} - exitForward={() => setActiveInput("cod")} - exitRight={() => setActiveInput("mor")} - hasFocused={() => { - setActiveInput("dom"); - props.actions.hasFocused?.(); - }} + focus={focus.childFocus("dom")} + deleteForward={() => focus.setActiveChild("mor")} + exitBackward={() => focus.setActiveChild("mor")} + exitForward={() => focus.setActiveChild("cod")} + exitRight={() => focus.setActiveChild("mor")} />
@@ -75,19 +73,15 @@ export function DiagramMorphismCellEditor(props: { }} morType={props.decl.morType} placeholder={props.theory.modelMorTypeMeta(props.decl.morType)?.name} - isActive={props.isActive && activeInput() === "mor"} + focus={focus.childFocus("mor")} deleteBackward={props.actions.deleteBackward} deleteForward={props.actions.deleteForward} exitBackward={props.actions.activateAbove} - exitForward={() => setActiveInput("dom")} + exitForward={() => focus.setActiveChild("dom")} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - exitLeft={() => setActiveInput("dom")} - exitRight={() => setActiveInput("cod")} - hasFocused={() => { - setActiveInput("mor"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("dom")} + exitRight={() => focus.setActiveChild("cod")} />
@@ -105,15 +99,11 @@ export function DiagramMorphismCellEditor(props: { obType={codType()} generateId={v7} isInvalid={codInvalid()} - isActive={props.isActive && activeInput() === "cod"} - deleteBackward={() => setActiveInput("mor")} - exitBackward={() => setActiveInput("dom")} + focus={focus.childFocus("cod")} + deleteBackward={() => focus.setActiveChild("mor")} + exitBackward={() => focus.setActiveChild("dom")} exitForward={props.actions.activateBelow} - exitLeft={() => setActiveInput("mor")} - hasFocused={() => { - setActiveInput("cod"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("mor")} />
); diff --git a/packages/frontend/src/diagram/object_cell_editor.tsx b/packages/frontend/src/diagram/object_cell_editor.tsx index 9fab4fe87..7f00403de 100644 --- a/packages/frontend/src/diagram/object_cell_editor.tsx +++ b/packages/frontend/src/diagram/object_cell_editor.tsx @@ -1,6 +1,4 @@ -import { createSignal } from "solid-js"; - -import { NameInput } from "catcolab-ui-components"; +import { NameInput, type FocusHandle, useChildFocus } from "catcolab-ui-components"; import type { DiagramObDecl } from "catlog-wasm"; import { ObInput } from "../model/object_input"; import type { CellActions } from "../notebook"; @@ -12,11 +10,12 @@ import "./object_cell_editor.css"; export function DiagramObjectCellEditor(props: { decl: DiagramObDecl; modifyDecl: (f: (decl: DiagramObDecl) => void) => void; - isActive: boolean; + focus: FocusHandle; actions: CellActions; theory: Theory; }) { - const [activeInput, setActiveInput] = createSignal("name"); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell. + const focus = useChildFocus(props.focus, { default: "name" }); return (
@@ -32,13 +31,9 @@ export function DiagramObjectCellEditor(props: { deleteForward={props.actions.deleteForward} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - exitRight={() => setActiveInput("overOb")} - exitForward={() => setActiveInput("overOb")} - isActive={props.isActive && activeInput() === "name"} - hasFocused={() => { - setActiveInput("name"); - props.actions.hasFocused?.(); - }} + exitRight={() => focus.setActiveChild("overOb")} + exitForward={() => focus.setActiveChild("overOb")} + focus={focus.childFocus("name")} /> setActiveInput("name")} - exitBackward={() => setActiveInput("name")} - isActive={props.isActive && activeInput() === "overOb"} - hasFocused={() => { - setActiveInput("overOb"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("name")} + exitBackward={() => focus.setActiveChild("name")} + focus={focus.childFocus("overOb")} />
); diff --git a/packages/frontend/src/model/contribution_cell_editor.tsx b/packages/frontend/src/model/contribution_cell_editor.tsx index 6288755f7..7e5d35149 100644 --- a/packages/frontend/src/model/contribution_cell_editor.tsx +++ b/packages/frontend/src/model/contribution_cell_editor.tsx @@ -1,7 +1,7 @@ -import { createMemo, createSignal, useContext, Switch, Match } from "solid-js"; +import { createMemo, useContext, Switch, Match } from "solid-js"; import invariant from "tiny-invariant"; -import { NameInput } from "catcolab-ui-components"; +import { NameInput, useChildFocus } from "catcolab-ui-components"; import type { Ob } from "catlog-wasm"; import { LiveModelContext } from "./context"; import { ContributionMonomialEditor } from "./contribution_monomial_editor"; @@ -32,7 +32,8 @@ export default function ContributionCellEditor( const liveModel = useContext(LiveModelContext); invariant(liveModel, "Live model should be provided as context"); - const [activeInput, setActiveInput] = createSignal("name"); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell. + const focus = useChildFocus(props.focus, { default: "name" }); const morTypeMeta = () => props.theory.modelMorTypeMeta(props.morphism.morType); @@ -97,19 +98,15 @@ export default function ContributionCellEditor( mor.name = name; }); }} - isActive={props.isActive && activeInput() === "name"} + focus={focus.childFocus("name")} deleteBackward={props.actions.deleteBackward} deleteForward={props.actions.deleteForward} exitBackward={props.actions.activateAbove} - exitForward={() => setActiveInput("cod")} + exitForward={() => focus.setActiveChild("cod")} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - exitLeft={() => setActiveInput("cod")} - exitRight={() => setActiveInput("dom")} - hasFocused={() => { - setActiveInput("name"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("cod")} + exitRight={() => focus.setActiveChild("dom")} />
:
@@ -131,15 +128,11 @@ export default function ContributionCellEditor( obType={codType()} applyOp={morTypeMeta()?.codomain?.apply} isInvalid={errors().some((err) => err.tag === "Cod" || err.tag === "CodType")} - isActive={props.isActive && activeInput() === "cod"} - deleteForward={() => setActiveInput("name")} + focus={focus.childFocus("cod")} + deleteForward={() => focus.setActiveChild("name")} exitBackward={props.actions.activateAbove} - exitForward={() => setActiveInput("dom")} - exitLeft={() => setActiveInput("name")} - hasFocused={() => { - setActiveInput("cod"); - props.actions.hasFocused?.(); - }} + exitForward={() => focus.setActiveChild("dom")} + exitLeft={() => focus.setActiveChild("name")} />
@@ -157,15 +150,11 @@ export default function ContributionCellEditor( setOb={setDomOb} obType={domType()} isInvalid={errors().some((err) => err.tag === "Dom" || err.tag === "DomType")} - isActive={props.isActive && activeInput() === "dom"} - deleteBackward={() => setActiveInput("name")} - exitBackward={() => setActiveInput("name")} + focus={focus.childFocus("dom")} + deleteBackward={() => focus.setActiveChild("name")} + exitBackward={() => focus.setActiveChild("name")} exitForward={props.actions.activateBelow} exitRight={props.actions.activateBelow} - hasFocused={() => { - setActiveInput("dom"); - props.actions.hasFocused?.(); - }} />
diff --git a/packages/frontend/src/model/contribution_monomial_editor.tsx b/packages/frontend/src/model/contribution_monomial_editor.tsx index 25d3680da..0c2343637 100644 --- a/packages/frontend/src/model/contribution_monomial_editor.tsx +++ b/packages/frontend/src/model/contribution_monomial_editor.tsx @@ -59,11 +59,12 @@ export function ContributionMonomialEditor(props: ContributionMonomialEditorProp return ( ob === null)} + when={(props.focus?.hasFocus() ?? props.isActive) || obList().some((ob) => ob === null)} fallback={
{ + props.focus?.setFocused(true); props.hasFocused?.(); evt.preventDefault(); }} @@ -91,14 +92,13 @@ export function ContributionMonomialEditor(props: ContributionMonomialEditorProp obType={props.obType} placeholder={props.placeholder} isInvalid={props.isInvalid} - isActive={props.isActive} + focus={props.focus} deleteBackward={props.deleteBackward} deleteForward={props.deleteForward} exitBackward={props.exitBackward} exitForward={props.exitForward} exitLeft={props.exitLeft} exitRight={props.exitRight} - hasFocused={props.hasFocused} insertKey={props.insertKey ?? ","} startDelimiter={
{"["}
} endDelimiter={
{"]"}
} diff --git a/packages/frontend/src/model/editors.ts b/packages/frontend/src/model/editors.ts index 516f19850..131d8595a 100644 --- a/packages/frontend/src/model/editors.ts +++ b/packages/frontend/src/model/editors.ts @@ -1,5 +1,6 @@ import type { Component } from "solid-js"; +import type { FocusHandle } from "catcolab-ui-components"; import type { MorDecl, ObDecl } from "catlog-wasm"; import type { CellActions } from "../notebook"; import type { Theory } from "../theory"; @@ -19,7 +20,7 @@ export type EditorVariantOverrides = { export type ObjectEditorProps = { object: ObDecl; modifyObject: (f: (decl: ObDecl) => void) => void; - isActive: boolean; + focus: FocusHandle; actions: CellActions; theory: Theory; }; @@ -28,7 +29,7 @@ export type ObjectEditorProps = { export type MorphismEditorProps = { morphism: MorDecl; modifyMorphism: (f: (decl: MorDecl) => void) => void; - isActive: boolean; + focus: FocusHandle; actions: CellActions; theory: Theory; }; diff --git a/packages/frontend/src/model/instantiation_cell_editor.tsx b/packages/frontend/src/model/instantiation_cell_editor.tsx index 9b034e5a8..09f4c1916 100644 --- a/packages/frontend/src/model/instantiation_cell_editor.tsx +++ b/packages/frontend/src/model/instantiation_cell_editor.tsx @@ -1,17 +1,13 @@ import type { DocInfo } from "catcolab-api/src/user_state"; -import { - batch, - createEffect, - createSignal, - Index, - Show, - splitProps, - untrack, - useContext, -} from "solid-js"; +import { batch, createEffect, Index, Show, splitProps, untrack, useContext } from "solid-js"; import invariant from "tiny-invariant"; -import { NameInput, type TextInputOptions } from "catcolab-ui-components"; +import { + type FocusHandle, + NameInput, + type TextInputOptions, + useChildFocus, +} from "catcolab-ui-components"; import type { DblModel, InstantiatedModel, Ob, SpecializeModel } from "catlog-wasm"; import { useApi } from "../api"; import { DocumentPicker, IdInput, IdInputPlaceholder } from "../components"; @@ -27,7 +23,7 @@ import "./instantiation_cell_editor.css"; export function InstantiationCellEditor(props: { instantiation: InstantiatedModel; modifyInstantiation: (f: (inst: InstantiatedModel) => void) => void; - isActive: boolean; + focus: FocusHandle; actions: CellActions; }) { const api = useApi(); @@ -67,14 +63,17 @@ export function InstantiationCellEditor(props: { invariant(models); const instantiated = models.useElaboratedModel(refId); - const [activeComponent, setActiveComponent] = createSignal( - refId() == null ? "model" : "name", - ); - const [activeIndex, setActiveIndex] = createSignal(0); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell. + const focus = useChildFocus(props.focus, { + default: refId() == null ? "model" : "name", + }); + const specializationFocus = useChildFocus(focus.childFocus("specializations"), { + default: 0, + }); const activateIndex = (index: number) => batch(() => { - setActiveComponent("specializations"); - setActiveIndex(index); + focus.setActiveChild("specializations"); + specializationFocus.setActiveChild(index); }); const insertSpecializationAtTop = () => { @@ -146,7 +145,7 @@ export function InstantiationCellEditor(props: { // Clean up empty rows when the cell becomes inactive. createEffect(() => { - if (!props.isActive) { + if (!props.focus.hasFocus()) { untrack(() => pruneEmptySpecializations()); } }); @@ -175,13 +174,9 @@ export function InstantiationCellEditor(props: { deleteForward={props.actions.deleteForward} exitUp={props.actions.activateAbove} exitDown={exitDownFromTop} - exitRight={() => setActiveComponent("model")} - exitForward={() => setActiveComponent("model")} - isActive={props.isActive && activeComponent() === "name"} - hasFocused={() => { - setActiveComponent("name"); - props.actions.hasFocused(); - }} + exitRight={() => focus.setActiveChild("model")} + exitForward={() => focus.setActiveChild("model")} + focus={focus.childFocus("name")} /> setActiveComponent("name")} + deleteBackward={() => focus.setActiveChild("name")} exitUp={props.actions.activateAbove} exitDown={exitDownFromTop} - exitLeft={() => setActiveComponent("name")} - exitBackward={() => setActiveComponent("name")} - isActive={props.isActive && activeComponent() === "model"} - hasFocused={() => { - setActiveComponent("model"); - props.actions.hasFocused(); - }} + exitLeft={() => focus.setActiveChild("name")} + exitBackward={() => focus.setActiveChild("name")} + focus={focus.childFocus("model")} />
    idInputTexts.set(i, text)} instantiatedModel={instantiated()?.validatedModel.model ?? null} - isActive={ - props.isActive && - activeComponent() === "specializations" && - activeIndex() === i - } - hasFocused={() => { - activateIndex(i); - props.actions.hasFocused(); - }} + focus={specializationFocus.childFocus(i)} createBelow={() => { props.modifyInstantiation((inst) => { const spec = { id: null, ob: null }; @@ -252,7 +235,7 @@ export function InstantiationCellEditor(props: { props.modifyInstantiation((inst) => inst.specializations.splice(i, 1), ); - i === 0 ? setActiveComponent("name") : activateIndex(i - 1); + i === 0 ? focus.setActiveChild("name") : activateIndex(i - 1); }} exitDown={() => { if (i >= props.instantiation.specializations.length - 1) { @@ -262,7 +245,7 @@ export function InstantiationCellEditor(props: { } }} exitUp={() => { - i === 0 ? setActiveComponent("name") : activateIndex(i - 1); + i === 0 ? focus.setActiveChild("name") : activateIndex(i - 1); }} /> @@ -273,7 +256,7 @@ export function InstantiationCellEditor(props: { class="model-specialization-add" onMouseDown={(evt) => { appendSpecialization(); - props.actions.hasFocused(); + props.focus.setFocused(true); evt.preventDefault(); }} > @@ -298,14 +281,13 @@ function SpecializationEditor( instantiatedModel: DblModel | null; /** Called when the displayed text of the id input changes. */ onIdTextChange?: (text: string) => void; - } & Pick< - TextInputOptions, - "isActive" | "hasFocused" | "createBelow" | "deleteBackward" | "exitDown" | "exitUp" - >, + focus: FocusHandle; + } & Pick, ) { const [inputProps, props] = splitProps(allProps, ["createBelow", "exitDown", "exitUp"]); - const [activeInput, setActiveInput] = createSignal("id"); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted row. + const focus = useChildFocus(props.focus, { default: "id" }); const obType = () => { const id = props.specialization.id; @@ -331,14 +313,10 @@ function SpecializationEditor( completions={props.instantiatedModel?.obGenerators()} idToLabel={(id) => props.instantiatedModel?.obGeneratorLabel(id)} labelToId={(label) => props.instantiatedModel?.obGeneratorWithLabel(label)} - isActive={props.isActive && activeInput() === "id"} - hasFocused={() => { - setActiveInput("id"); - props.hasFocused?.(); - }} + focus={focus.childFocus("id")} deleteBackward={props.deleteBackward} - exitForward={() => setActiveInput("ob")} - exitRight={() => setActiveInput("ob")} + exitForward={() => focus.setActiveChild("ob")} + exitRight={() => focus.setActiveChild("ob")} {...inputProps} /> @@ -353,14 +331,10 @@ function SpecializationEditor( }); }} obType={obType()} - isActive={props.isActive && activeInput() === "ob"} - hasFocused={() => { - setActiveInput("ob"); - props.hasFocused?.(); - }} - deleteBackward={() => setActiveInput("id")} - exitBackward={() => setActiveInput("id")} - exitLeft={() => setActiveInput("id")} + focus={focus.childFocus("ob")} + deleteBackward={() => focus.setActiveChild("id")} + exitBackward={() => focus.setActiveChild("id")} + exitLeft={() => focus.setActiveChild("id")} {...inputProps} /> )} diff --git a/packages/frontend/src/model/model_editor.tsx b/packages/frontend/src/model/model_editor.tsx index f5d4250a9..8236b2e21 100644 --- a/packages/frontend/src/model/model_editor.tsx +++ b/packages/frontend/src/model/model_editor.tsx @@ -4,6 +4,7 @@ import invariant from "tiny-invariant"; import { Model, Nb } from "catcolab-document-methods"; import type { InstantiatedModel, ModelJudgment, MorDecl, ObDecl } from "catcolab-document-types"; +import { type FocusHandle } from "catcolab-ui-components"; import { type CellConstructor, type FormalCellEditorProps, NotebookEditor } from "../notebook"; import { TheoryLibraryContext, type ModelTypeMeta, type Theory } from "../theory"; import { LiveModelContext } from "./context"; @@ -12,7 +13,7 @@ import { InstantiationCellEditor } from "./instantiation_cell_editor"; /** Notebook editor for a model of a double theory. */ -export function ModelNotebookEditor(props: { liveModel: LiveModelDoc }) { +export function ModelNotebookEditor(props: { liveModel: LiveModelDoc; focus: FocusHandle }) { const liveDoc = () => props.liveModel.liveDoc; const cellConstructors = () => { @@ -34,6 +35,7 @@ export function ModelNotebookEditor(props: { liveModel: LiveModelDoc }) { cellConstructors={cellConstructors()} cellLabel={judgmentLabel} duplicateCell={Model.duplicateModelJudgment} + focus={props.focus} /> ); @@ -65,7 +67,7 @@ export function ModelCellEditor(props: FormalCellEditorProps) { modifyObject={(f: (decl: ObDecl) => void) => props.changeContent((content) => f(content as ObDecl)) } - isActive={props.isActive} + focus={props.focus} actions={props.actions} theory={theory()} /> @@ -85,7 +87,7 @@ export function ModelCellEditor(props: FormalCellEditorProps) { modifyMorphism={(f: (decl: MorDecl) => void) => props.changeContent((content) => f(content as MorDecl)) } - isActive={props.isActive} + focus={props.focus} actions={props.actions} theory={theory()} /> @@ -98,7 +100,7 @@ export function ModelCellEditor(props: FormalCellEditorProps) { modifyInstantiation={(f) => props.changeContent((content) => f(content as InstantiatedModel)) } - isActive={props.isActive} + focus={props.focus} actions={props.actions} /> diff --git a/packages/frontend/src/model/morphism_cell_editor.tsx b/packages/frontend/src/model/morphism_cell_editor.tsx index a3aeb4ebe..b7d459044 100644 --- a/packages/frontend/src/model/morphism_cell_editor.tsx +++ b/packages/frontend/src/model/morphism_cell_editor.tsx @@ -1,7 +1,7 @@ -import { createMemo, createSignal, useContext } from "solid-js"; +import { createMemo, useContext } from "solid-js"; import invariant from "tiny-invariant"; -import { NameInput } from "catcolab-ui-components"; +import { NameInput, useChildFocus } from "catcolab-ui-components"; import { removeProxyAndCopy } from "../util/remove_proxy_and_copy"; import { LiveModelContext } from "./context"; import type { MorphismEditorProps } from "./editors"; @@ -16,7 +16,8 @@ export default function MorphismCellEditor(props: MorphismEditorProps) { const liveModel = useContext(LiveModelContext); invariant(liveModel, "Live model should be provided as context"); - const [activeInput, setActiveInput] = createSignal("name"); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell. + const focus = useChildFocus(props.focus, { default: "name" }); const morTypeMeta = () => props.theory.modelMorTypeMeta(props.morphism.morType); @@ -74,15 +75,11 @@ export default function MorphismCellEditor(props: MorphismEditorProps) { obType={domType()} applyOp={morTypeMeta()?.domain?.apply} isInvalid={errors().some((err) => err.tag === "Dom" || err.tag === "DomType")} - isActive={props.isActive && activeInput() === "dom"} - deleteForward={() => setActiveInput("name")} - exitBackward={() => setActiveInput("name")} - exitForward={() => setActiveInput("cod")} - exitRight={() => setActiveInput("name")} - hasFocused={() => { - setActiveInput("dom"); - props.actions.hasFocused?.(); - }} + focus={focus.childFocus("dom")} + deleteForward={() => focus.setActiveChild("name")} + exitBackward={() => focus.setActiveChild("name")} + exitForward={() => focus.setActiveChild("cod")} + exitRight={() => focus.setActiveChild("name")} />
    @@ -95,19 +92,15 @@ export default function MorphismCellEditor(props: MorphismEditorProps) { mor.name = name; }); }} - isActive={props.isActive && activeInput() === "name"} + focus={focus.childFocus("name")} deleteBackward={props.actions.deleteBackward} deleteForward={props.actions.deleteForward} exitBackward={props.actions.activateAbove} - exitForward={() => setActiveInput("dom")} + exitForward={() => focus.setActiveChild("dom")} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - exitLeft={() => setActiveInput("dom")} - exitRight={() => setActiveInput("cod")} - hasFocused={() => { - setActiveInput("name"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("dom")} + exitRight={() => focus.setActiveChild("cod")} />
    @@ -126,15 +119,11 @@ export default function MorphismCellEditor(props: MorphismEditorProps) { obType={codType()} applyOp={morTypeMeta()?.codomain?.apply} isInvalid={errors().some((err) => err.tag === "Cod" || err.tag === "CodType")} - isActive={props.isActive && activeInput() === "cod"} - deleteBackward={() => setActiveInput("name")} - exitBackward={() => setActiveInput("dom")} + focus={focus.childFocus("cod")} + deleteBackward={() => focus.setActiveChild("name")} + exitBackward={() => focus.setActiveChild("dom")} exitForward={props.actions.activateBelow} - exitLeft={() => setActiveInput("name")} - hasFocused={() => { - setActiveInput("cod"); - props.actions.hasFocused?.(); - }} + exitLeft={() => focus.setActiveChild("name")} />
    diff --git a/packages/frontend/src/model/object_cell_editor.tsx b/packages/frontend/src/model/object_cell_editor.tsx index 46c5e1520..c1e2e92d0 100644 --- a/packages/frontend/src/model/object_cell_editor.tsx +++ b/packages/frontend/src/model/object_cell_editor.tsx @@ -23,14 +23,13 @@ export default function ObjectCellEditor(props: ObjectEditorProps) { ob.name = name; }); }} - isActive={props.isActive} + focus={props.focus} deleteBackward={props.actions.deleteBackward} deleteForward={props.actions.deleteForward} exitBackward={props.actions.activateAbove} exitForward={props.actions.activateBelow} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - hasFocused={props.actions.hasFocused} /> ); diff --git a/packages/frontend/src/model/object_list_editor.tsx b/packages/frontend/src/model/object_list_editor.tsx index 73975ab4f..a074aeb43 100644 --- a/packages/frontend/src/model/object_list_editor.tsx +++ b/packages/frontend/src/model/object_list_editor.tsx @@ -1,7 +1,6 @@ import { batch, createEffect, - createSignal, Index, type JSX, mergeProps, @@ -11,7 +10,7 @@ import { } from "solid-js"; import invariant from "tiny-invariant"; -import type { TextInputOptions } from "catcolab-ui-components"; +import { type FocusHandle, type TextInputOptions, useChildFocus } from "catcolab-ui-components"; import type { Ob, QualifiedName } from "catlog-wasm"; import { ObIdInput } from "../components"; import { removeProxyAndCopy } from "../util/remove_proxy_and_copy"; @@ -44,7 +43,17 @@ export function ObListEditor(originalProps: ObListEditorProps) { const liveModel = useContext(LiveModelContext); invariant(liveModel, "Live model should be provided as context"); - const [activeIndex, setActiveIndex] = createSignal(0); + const parentFocus: FocusHandle = { + hasFocus: () => props.focus?.hasFocus() ?? !!props.isActive, + setFocused: (focused) => { + if (props.focus) { + props.focus.setFocused(focused); + } else if (focused) { + props.hasFocused?.(); + } + }, + }; + const focus = useChildFocus(parentFocus, { default: 0 }); // Track which indices have non-empty text (including incomplete input). const inputTexts = new Map(); @@ -73,7 +82,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { updateObList((objects) => { objects.splice(i, 0, null); }); - setActiveIndex(i); + focus.setActiveChild(i); }); }; @@ -89,7 +98,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { // Insert into new object into empty list when focus is gained. createEffect(() => { - if (props.isActive && untrack(obList).length === 0) { + if (parentFocus.hasFocus() && untrack(obList).length === 0) { insertNewOb(0); } }); @@ -104,7 +113,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { // Clean up when the component becomes inactive. createEffect(() => { - if (!props.isActive) { + if (!parentFocus.hasFocus()) { untrack(() => deactivate()); } }); @@ -115,7 +124,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { onMouseDown={(evt) => { if (obList().length === 0) { insertNewOb(0); - props.hasFocused?.(); + parentFocus.setFocused(true); evt.preventDefault(); } }} @@ -139,7 +148,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { liveModel().elaboratedModel()?.obGeneratorWithLabel(label) } completions={completions()} - isActive={props.isActive && activeIndex() === i} + focus={focus.childFocus(i)} deleteBackward={() => batch(() => { updateObList((objects) => { @@ -148,7 +157,7 @@ export function ObListEditor(originalProps: ObListEditorProps) { if (i === 0) { props.deleteBackward?.(); } else { - setActiveIndex(i - 1); + focus.setActiveChild(i - 1); } }) } @@ -168,14 +177,14 @@ export function ObListEditor(originalProps: ObListEditorProps) { if (i === 0) { props.exitLeft?.(); } else { - setActiveIndex(i - 1); + focus.setActiveChild(i - 1); } }} exitRight={() => { if (i === obList().length - 1) { props.exitRight?.(); } else { - setActiveIndex(i + 1); + focus.setActiveChild(i + 1); } }} interceptKeyDown={(evt) => { @@ -184,16 +193,12 @@ export function ObListEditor(originalProps: ObListEditorProps) { return true; } else if (evt.key === "Home" && !evt.shiftKey) { // TODO: Should move to beginning of input. - setActiveIndex(0); + focus.setActiveChild(0); } else if (evt.key === "End" && !evt.shiftKey) { - setActiveIndex(obList().length - 1); + focus.setActiveChild(obList().length - 1); } return false; }} - hasFocused={() => { - setActiveIndex(i); - props.hasFocused?.(); - }} /> )} diff --git a/packages/frontend/src/model/string_diagram_morphism_cell_editor.tsx b/packages/frontend/src/model/string_diagram_morphism_cell_editor.tsx index 7b98c26ff..9731338aa 100644 --- a/packages/frontend/src/model/string_diagram_morphism_cell_editor.tsx +++ b/packages/frontend/src/model/string_diagram_morphism_cell_editor.tsx @@ -1,7 +1,7 @@ import { Index, createEffect, createMemo, createSignal, untrack, useContext } from "solid-js"; import invariant from "tiny-invariant"; -import { NameInput } from "catcolab-ui-components"; +import { type FocusHandle, NameInput } from "catcolab-ui-components"; import type { Ob, ObOp, ObType, QualifiedName } from "catlog-wasm"; import { ObIdInput } from "../components"; import { removeProxyAndCopy } from "../util/remove_proxy_and_copy"; @@ -34,7 +34,7 @@ function WireColumn(props: { exitFirstBackward: (() => void) | undefined; /** Called when tabbing forward from the last wire. */ exitLastForward: (() => void) | undefined; - hasFocused: (() => void) | undefined; + setFocused: () => void; }) { const liveModel = useContext(LiveModelContext); invariant(liveModel, "Live model should be provided as context"); @@ -90,7 +90,7 @@ function WireColumn(props: { }} hasFocused={() => { props.activateWire(i); - props.hasFocused?.(); + props.setFocused(); }} /> ); @@ -110,7 +110,7 @@ function WireColumn(props: { class={`${styles.wire} ${styles.addWire}`} onMouseDown={(evt) => { props.insertWire(props.obs.length); - props.hasFocused?.(); + props.setFocused(); evt.preventDefault(); }} > @@ -134,6 +134,13 @@ export default function StringDiagramMorphismCellEditor(props: MorphismEditorPro const [active, setActive] = createSignal({ zone: "name" }); + // Reset to default on deactivation so re-entry lands on the name input. + createEffect(() => { + if (!props.focus.hasFocus()) { + setActive({ zone: "name" }); + } + }); + // Track which wire indices have non-empty text (including incomplete input). const domInputTexts = new Map(); const codInputTexts = new Map(); @@ -234,13 +241,23 @@ export default function StringDiagramMorphismCellEditor(props: MorphismEditorPro // Clean up when the cell becomes inactive. createEffect(() => { - if (!props.isActive) { + if (!props.focus.hasFocus()) { untrack(() => deactivate()); } }); const completions = () => liveModel().elaboratedModel()?.obGeneratorsWithType(elementObType()); + const nameFocus: FocusHandle = { + hasFocus: () => props.focus.hasFocus() && active()?.zone === "name", + setFocused: (focused) => { + if (focused) { + setActive({ zone: "name" }); + props.focus.setFocused(true); + } + }, + }; + const errors = () => { const validated = liveModel().validatedModel(); if (validated?.tag !== "Invalid") { @@ -258,7 +275,7 @@ export default function StringDiagramMorphismCellEditor(props: MorphismEditorPro completions={completions()} isActive={(i) => { const a = active(); - return props.isActive && a?.zone === "dom" && a.index === i; + return props.focus.hasFocus() && a?.zone === "dom" && a.index === i; }} onTextChange={(i, text) => domInputTexts.set(i, text)} insertWire={insertDom} @@ -278,7 +295,7 @@ export default function StringDiagramMorphismCellEditor(props: MorphismEditorPro insertCod(0); } }} - hasFocused={props.actions.hasFocused} + setFocused={() => props.focus.setFocused(true)} />
    { - setActive({ zone: "name" }); - props.actions.hasFocused?.(); - }} />
    { const a = active(); - return props.isActive && a?.zone === "cod" && a.index === i; + return props.focus.hasFocus() && a?.zone === "cod" && a.index === i; }} onTextChange={(i, text) => codInputTexts.set(i, text)} insertWire={insertCod} @@ -349,7 +362,7 @@ export default function StringDiagramMorphismCellEditor(props: MorphismEditorPro } }} exitLastForward={props.actions.activateBelow} - hasFocused={props.actions.hasFocused} + setFocused={() => props.focus.setFocused(true)} /> ); diff --git a/packages/frontend/src/notebook/notebook_cell.tsx b/packages/frontend/src/notebook/notebook_cell.tsx index 07b6cf7db..f6794ba07 100644 --- a/packages/frontend/src/notebook/notebook_cell.tsx +++ b/packages/frontend/src/notebook/notebook_cell.tsx @@ -17,7 +17,7 @@ import type { EditorView } from "prosemirror-view"; import { createEffect, createSignal, type JSX, onCleanup, Show } from "solid-js"; import type { Uuid } from "catcolab-document-types"; -import { type Completion, Completions, IconButton } from "catcolab-ui-components"; +import { type Completion, Completions, type FocusHandle, IconButton } from "catcolab-ui-components"; import { RichTextEditor } from "../components"; import { CellTypePopover } from "./notebook_editor"; @@ -25,11 +25,8 @@ import "./notebook_cell.css"; /** Props available to all notebook cell editors. */ export type CellEditorProps = { - /** Is the cell requested to be active? - - When this prop changes to `true`, the cell is authorizeed to grab the focus. - */ - isActive: boolean; + /** Focus state for this cell. */ + focus: FocusHandle; /** Actions invokable within the cell. */ actions: CellActions; @@ -61,9 +58,6 @@ export type CellActions = { /** Move this cell down, if possible. */ moveDown: () => void; - - /** The cell has received focus. */ - hasFocused: () => void; }; const cellDragDataKey = Symbol("notebook-cell"); @@ -99,6 +93,7 @@ the cell is rendered by its children. export function NotebookCell(props: { cellId: Uuid; index: number; + focus: FocusHandle; actions: CellActions; children: JSX.Element; tag?: string; @@ -235,6 +230,7 @@ export function NotebookCell(props: {
    { const view = editorView(); - if (props.isActive && view) { + if (props.focus.hasFocus() && view) { view.focus(); } }); @@ -315,7 +311,7 @@ export function RichTextCellEditor( deleteForward={props.actions.deleteForward} exitUp={props.actions.activateAbove} exitDown={props.actions.activateBelow} - onFocus={props.actions.hasFocused} + onFocus={() => props.focus.setFocused(true)} /> ); } diff --git a/packages/frontend/src/notebook/notebook_editor.tsx b/packages/frontend/src/notebook/notebook_editor.tsx index 6187fbd67..b53d281f3 100644 --- a/packages/frontend/src/notebook/notebook_editor.tsx +++ b/packages/frontend/src/notebook/notebook_editor.tsx @@ -18,15 +18,17 @@ import { import invariant from "tiny-invariant"; import { Nb } from "catcolab-document-methods"; -import type { Cell, Notebook } from "catcolab-document-types"; +import type { Cell, Notebook, Uuid } from "catcolab-document-types"; import { type Completion, Completions, type CompletionsRef, + type FocusHandle, IconButton, type KbdKey, keyEventHasModifier, type ModifierKey, + useChildFocus, } from "catcolab-ui-components"; import { materializeFromAutomerge } from "../util/materialize_from_automerge"; import { @@ -42,10 +44,10 @@ import "./notebook_editor.css"; /** Identifies which create-cell popover, if any, the editor wants open. -Either an index of an existing cell (open the "create below" popover anchored -to that cell) or `"append"` (open the popover for the end-of-notebook button). +Either the id of an existing cell (open the "create below" popover anchored to +that cell) or `"append"` (open the popover for the end-of-notebook button). */ -type CreatePopoverTarget = number | "append" | null; +type CreatePopoverTarget = Uuid | "append" | null; /** Constructor for a cell in a notebook. @@ -86,17 +88,16 @@ export function NotebookEditor(props: { formalCellEditor: Component>; cellConstructors?: CellConstructor[]; cellLabel?: (content: T) => string | undefined; + focus: FocusHandle; /** Called to duplicate an existing cell. If omitted, a deep copy is performed. */ duplicateCell?: (content: T) => T; - - // FIXME: Remove this option once we fix focus management. - noShortcuts?: boolean; }) { - const [activeCell, setActiveCell] = createSignal(null); + // oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted notebook. + const cellFocus = useChildFocus(props.focus); const [currentDropTarget, setCurrentDropTarget] = createSignal(null); // Which create-cell popover (if any) the editor has requested to open. @@ -113,13 +114,20 @@ export function NotebookEditor(props: { description, shortcut: shortcut && [cellShortcutModifier, ...shortcut], onComplete: () => { - const [i, n] = [activeCell(), props.notebook.cellOrder.length]; - const cellIndex = i != null ? Math.min(i + 1, n) : n; + const activeCellId = cellFocus.activeChild(); + const activeIndex = activeCellId + ? props.notebook.cellOrder.indexOf(activeCellId) + : -1; + const cellIndex = + activeIndex >= 0 + ? Math.min(activeIndex + 1, props.notebook.cellOrder.length) + : props.notebook.cellOrder.length; + const newCell = cc.construct(); props.changeNotebook((nb) => { - Nb.insertCellAtIndex(nb, cc.construct(), cellIndex); + Nb.insertCellAtIndex(nb, newCell, cellIndex); }); // Defer so the popover fully closes before we focus the new cell. - requestAnimationFrame(() => setActiveCell(cellIndex)); + requestAnimationFrame(() => cellFocus.setActiveChild(newCell.id)); }, }; }); @@ -144,11 +152,12 @@ export function NotebookEditor(props: { shortcut: shortcut && [cellShortcutModifier, ...shortcut], onComplete: () => { const index = i + 1; + const newCell = cc.construct(); props.changeNotebook((nb) => { - Nb.insertCellAtIndex(nb, cc.construct(), index); + Nb.insertCellAtIndex(nb, newCell, index); }); // Defer so the popover fully closes before we focus the new cell. - requestAnimationFrame(() => setActiveCell(index)); + requestAnimationFrame(() => cellFocus.setActiveChild(newCell.id)); }, }; }); @@ -162,19 +171,18 @@ export function NotebookEditor(props: { description, shortcut: shortcut && [cellShortcutModifier, ...shortcut], onComplete: () => { + const newCell = cc.construct(); props.changeNotebook((nb) => { - Nb.appendCell(nb, cc.construct()); + Nb.appendCell(nb, newCell); }); // Defer so the popover fully closes before we focus the new cell. - requestAnimationFrame(() => { - setActiveCell(Nb.numCells(props.notebook) - 1); - }); + requestAnimationFrame(() => cellFocus.setActiveChild(newCell.id)); }, }; }); makeEventListener(window, "keydown", (evt) => { - if (props.noShortcuts) { + if (!props.focus.hasFocus()) { return; } if (keyEventHasModifier(evt, cellShortcutModifier)) { @@ -195,8 +203,8 @@ export function NotebookEditor(props: { // create-cell popover. The popover is rendered by that anchor, so // it is positioned automatically and doesn't require any DOM // queries here. - const cellIndex = activeCell(); - setCreatePopoverTarget(cellIndex != null ? cellIndex : "append"); + const cellId = cellFocus.activeChild(); + setCreatePopoverTarget(cellId ?? "append"); evt.preventDefault(); // Stop the same event from reaching `CellTypePopover`'s window // keydown listener, which would otherwise see the popover as @@ -255,21 +263,12 @@ export function NotebookEditor(props: { }); return ( -
    { - const container = evt.currentTarget; - setTimeout(() => { - if (!container.contains(document.activeElement)) { - setActiveCell(null); - } - }, 0); - }} - > +
    setCreatePopoverTarget(open ? "append" : null)} > @@ -281,32 +280,41 @@ export function NotebookEditor(props: {
      {(cellId, i) => { - const isActive = () => activeCell() === i(); + const focus = () => cellFocus.childFocus(cellId); const cellActions: CellActions = { activateAbove() { if (i() > 0) { - setActiveCell(i() - 1); + cellFocus.setActiveChild( + props.notebook.cellOrder[i() - 1] ?? null, + ); } }, activateBelow() { if (i() < Nb.numCells(props.notebook) - 1) { - setActiveCell(i() + 1); + cellFocus.setActiveChild( + props.notebook.cellOrder[i() + 1] ?? null, + ); } }, deleteBackward() { const index = i(); + const nextActiveId = props.notebook.cellOrder[index - 1] ?? null; props.changeNotebook((nb) => { Nb.deleteCellAtIndex(nb, index); }); - setActiveCell(index - 1); + cellFocus.setActiveChild(nextActiveId); }, deleteForward() { const index = i(); + const nextActiveId = + props.notebook.cellOrder[index + 1] ?? + props.notebook.cellOrder[index - 1] ?? + null; props.changeNotebook((nb) => { Nb.deleteCellAtIndex(nb, index); }); - setActiveCell(index); + cellFocus.setActiveChild(nextActiveId); }, moveUp() { // oxlint-disable-next-line solid/reactivity -- event handler @@ -320,9 +328,6 @@ export function NotebookEditor(props: { Nb.moveCellDown(nb, i()); }); }, - hasFocused() { - setActiveCell(i()); - }, }; const cell = props.notebook.cellContents[cellId]; @@ -345,7 +350,7 @@ export function NotebookEditor(props: { props.changeNotebook((nb) => { Nb.insertCellAtIndex(nb, newCell, index + 1); }); - setActiveCell(index + 1); + cellFocus.setActiveChild(newCell.id); }; } @@ -354,6 +359,7 @@ export function NotebookEditor(props: { (props: { : undefined } createCompletions={createBelowCommands(i())} - popoverOpen={createPopoverTarget() === i()} + popoverOpen={createPopoverTarget() === cellId} setPopoverOpen={(open) => - setCreatePopoverTarget(open ? i() : null) + setCreatePopoverTarget(open ? cellId : null) } currentDropTarget={currentDropTarget()} setCurrentDropTarget={setCurrentDropTarget} @@ -374,7 +380,7 @@ export function NotebookEditor(props: { cellId={cell.id} handle={props.handle} path={[...props.path, "cellContents", cell.id]} - isActive={isActive()} + focus={focus()} actions={cellActions} /> @@ -391,7 +397,7 @@ export function NotebookEditor(props: { ), ) } - isActive={isActive()} + focus={focus()} actions={cellActions} /> )} @@ -407,6 +413,7 @@ export function NotebookEditor(props: {
      setCreatePopoverTarget(open ? "append" : null)} @@ -428,6 +435,7 @@ Up/Down to move, Enter to select, Escape to close). */ export function CellTypePopover(props: { completions: Completion[]; + focus?: FocusHandle; tooltip?: string; /** Whether the button is visible. Defaults to `true`. The button always remains visible while the popover is open. */ @@ -454,6 +462,9 @@ export function CellTypePopover(props: { if (!isOpen()) { return; } + if (props.focus && !props.focus.hasFocus()) { + return; + } const ref = completionsRef(); if (evt.key === "ArrowDown") { ref?.nextPresumptive(); diff --git a/packages/frontend/src/page/document_page.tsx b/packages/frontend/src/page/document_page.tsx index 441734f45..5ea18b825 100644 --- a/packages/frontend/src/page/document_page.tsx +++ b/packages/frontend/src/page/document_page.tsx @@ -1,4 +1,5 @@ import Resizable, { type ContextValue } from "@corvu/resizable"; +import { makeEventListener } from "@solid-primitives/event-listener"; import { Title } from "@solidjs/meta"; import { useNavigate, useParams } from "@solidjs/router"; import ChevronsRight from "lucide-solid/icons/chevrons-right"; @@ -17,7 +18,17 @@ import { } from "solid-js"; import invariant from "tiny-invariant"; -import { Button, IconButton, ResizableHandle, WarningBanner } from "catcolab-ui-components"; +import { + Button, + type FocusHandle, + IconButton, + keyEventHasModifier, + primaryModifier, + ResizableHandle, + rootFocus, + useChildFocus, + WarningBanner, +} from "catcolab-ui-components"; import { getLiveAnalysis, type LiveAnalysisDoc } from "../analysis"; import { AnalysisNotebookEditor } from "../analysis/analysis_editor"; import { AnalysisInfo } from "../analysis/analysis_info"; @@ -63,6 +74,7 @@ export default function DocumentPage() { const params = useParams(); const navigate = useNavigate(); const isSidePanelOpen = () => !!params.subkind && !!params.subref; + const paneFocus = useChildFocus<"primary" | "secondary">(rootFocus, { default: "primary" }); // Redirect if primary and secondary refs match createEffect(() => { @@ -101,6 +113,7 @@ export default function DocumentPage() { ); const closeSidePanel = () => { + paneFocus.setActiveChild("primary"); navigate(`/${params.kind}/${params.ref}`); }; @@ -120,6 +133,7 @@ export default function DocumentPage() { // expand the second panel context?.expand(1); } else { + paneFocus.setActiveChild("primary"); // collapse the second panel context?.collapse(1); // Set the first panel to be the full size @@ -161,6 +175,8 @@ export default function DocumentPage() { closeSidePanel={closeSidePanel} togglePrimaryHistorySidebar={togglePrimaryHistorySidebar} toggleSecondaryHistorySidebar={toggleSecondaryHistorySidebar} + primaryPaneFocus={paneFocus.childFocus("primary")} + secondaryPaneFocus={paneFocus.childFocus("secondary")} /> } sidebarContents={ @@ -178,6 +194,8 @@ export default function DocumentPage() { } : undefined; })()} + primaryPaneFocus={paneFocus.childFocus("primary")} + secondaryPaneFocus={paneFocus.childFocus("secondary")} refetchPrimaryDoc={refetchPrimaryDoc} refetchSecondaryDoc={refetchSecondaryDoc} /> @@ -194,6 +212,8 @@ export default function DocumentPage() { setResizableContext={setResizableContext} primaryHistoryOpen={primaryHistoryOpen()} secondaryHistoryOpen={secondaryHistoryOpen()} + primaryPaneFocus={paneFocus.childFocus("primary")} + secondaryPaneFocus={paneFocus.childFocus("secondary")} /> )} @@ -212,6 +232,8 @@ function SplitPaneToolbar(props: { maximizeSidePanel: () => void; togglePrimaryHistorySidebar: () => void; toggleSecondaryHistorySidebar: () => void; + primaryPaneFocus: FocusHandle; + secondaryPaneFocus: FocusHandle; }) { const secondaryPanelSize = () => props.panelSizes?.[1]; const primaryPanelSize = () => props.panelSizes?.[0]; @@ -221,7 +243,13 @@ function SplitPaneToolbar(props: { - + { + props.primaryPaneFocus.setFocused(true); + props.togglePrimaryHistorySidebar(); + }} + tooltip="Toggle history" + > @@ -232,7 +260,10 @@ function SplitPaneToolbar(props: { style={{ left: `${(primaryPanelSize() ?? 0) * 100}%` }} > { + props.primaryPaneFocus.setFocused(true); + props.togglePrimaryHistorySidebar(); + }} tooltip="Toggle history" > @@ -249,6 +280,7 @@ function SplitPaneToolbar(props: { closeSidePanel={props.closeSidePanel} maximizeSidePanel={props.maximizeSidePanel} toggleHistorySidebar={props.toggleSecondaryHistorySidebar} + secondaryPaneFocus={props.secondaryPaneFocus} /> )} @@ -263,6 +295,7 @@ function SecondaryToolbar(props: { closeSidePanel: () => void; maximizeSidePanel: () => void; toggleHistorySidebar: () => void; + secondaryPaneFocus: FocusHandle; }) { return ( <> @@ -288,7 +321,13 @@ function SecondaryToolbar(props: { > {(secondary) => (
      - + { + props.secondaryPaneFocus.setFocused(true); + props.toggleHistorySidebar(); + }} + tooltip="Toggle history" + > void; primaryHistoryOpen: boolean; secondaryHistoryOpen: boolean; + primaryPaneFocus: FocusHandle; + secondaryPaneFocus: FocusHandle; }) { return ( @@ -329,6 +370,7 @@ function ResizablePanels(props: { refetchPrimaryDoc={props.refetchPrimaryDoc} refetchSecondaryDoc={props.refetchSecondaryDoc} historySidebarOpen={props.primaryHistoryOpen} + focus={props.primaryPaneFocus} /> @@ -347,6 +389,7 @@ function ResizablePanels(props: { refetchPrimaryDoc={props.refetchPrimaryDoc} refetchSecondaryDoc={props.refetchSecondaryDoc} historySidebarOpen={props.secondaryHistoryOpen} + focus={props.secondaryPaneFocus} /> )} @@ -365,6 +408,7 @@ export function DocumentPane(props: { refetchPrimaryDoc: () => void; refetchSecondaryDoc: () => void; historySidebarOpen: boolean; + focus: FocusHandle; }) { const api = useApi(); const [isDeleted, setIsDeleted] = createSignal(false); @@ -401,10 +445,33 @@ export function DocumentPane(props: { const history = useSnapshotHistory(() => props.docRef.refId); + makeEventListener(window, "keydown", (evt) => { + if (!props.focus.hasFocus()) { + return; + } + const key = evt.key.toUpperCase(); + const hasPrimary = keyEventHasModifier(evt, primaryModifier); + if (!hasPrimary || evt.altKey) { + return; + } + + if (key === "Z" && !evt.shiftKey && history.canUndo()) { + history.onUndo(); + return evt.preventDefault(); + } + if ( + ((key === "Z" && evt.shiftKey) || (key === "Y" && !evt.shiftKey)) && + history.canRedo() + ) { + history.onRedo(); + return evt.preventDefault(); + } + }); + // oxlint-disable solid/reactivity -- Context.Provider value getter is reactive return ( props.docRef.refId}> -
      +
      props.focus.setFocused(true)}>
      - {(liveModel) => } + {(liveModel) => ( + + )} {(liveDiagram) => ( - + )} {(liveAnalysis) => ( - + )} diff --git a/packages/frontend/src/page/document_page_sidebar.tsx b/packages/frontend/src/page/document_page_sidebar.tsx index 29c34f6b7..64f249fe9 100644 --- a/packages/frontend/src/page/document_page_sidebar.tsx +++ b/packages/frontend/src/page/document_page_sidebar.tsx @@ -2,7 +2,7 @@ import { useNavigate } from "@solidjs/router"; import { createMemo, createResource, For, Show, useContext } from "solid-js"; import { stringify as uuidStringify } from "uuid"; -import { DocumentTypeIcon } from "catcolab-ui-components"; +import { DocumentTypeIcon, type FocusHandle } from "catcolab-ui-components"; import type { Document, Link } from "catlog-wasm"; import { type Api, type LiveDocWithRef, useApi } from "../api"; import { TheoryLibraryContext } from "../theory"; @@ -12,6 +12,8 @@ import { DocumentMenu } from "./document_menu"; export function DocumentSidebar(props: { primaryDoc?: LiveDocWithRef; secondaryDoc?: LiveDocWithRef; + primaryPaneFocus: FocusHandle; + secondaryPaneFocus: FocusHandle; refetchPrimaryDoc: () => void; refetchSecondaryDoc: () => void; }) { @@ -21,6 +23,8 @@ export function DocumentSidebar(props: { @@ -59,6 +63,8 @@ async function getDocParent(doc: Document, api: Api): Promise void; refetchSecondaryDoc: () => void; }) { @@ -78,6 +84,8 @@ function RelatedDocuments(props: { indent={1} primaryDoc={props.primaryDoc} secondaryDoc={props.secondaryDoc} + primaryPaneFocus={props.primaryPaneFocus} + secondaryPaneFocus={props.secondaryPaneFocus} refetchPrimaryDoc={props.refetchPrimaryDoc} refetchSecondaryDoc={props.refetchSecondaryDoc} /> @@ -92,6 +100,8 @@ function DocumentsTreeNode(props: { indent: number; primaryDoc: LiveDocWithRef; secondaryDoc?: LiveDocWithRef; + primaryPaneFocus: FocusHandle; + secondaryPaneFocus: FocusHandle; refetchPrimaryDoc: () => void; refetchSecondaryDoc: () => void; }) { @@ -114,12 +124,22 @@ function DocumentsTreeNode(props: { .map((rel) => uuidStringify(rel.refId)); }); - // oxlint-disable-next-line solid/reactivity -- createResource fetcher - const [childDocs] = createResource(childRefIds, async (refIds) => { + const childDocSource = createMemo(() => { + const refIds = childRefIds(); + return { + createdAtByRefId: Object.fromEntries( + refIds.map((refId) => [refId, userState.documents[refId]?.createdAt ?? 0]), + ), + isParentOwnerless: props.doc.docRef.permissions.anyone === "Own", + refIds, + }; + }); + + const [childDocs] = createResource(childDocSource, async (source) => { // Individual failures are skipped to prevent one corrupt document // from crashing the entire sidebar. const childDocs = await Promise.all( - refIds.map(async (refId) => { + source.refIds.map(async (refId) => { try { return await api.getLiveDoc(refId); } catch (e) { @@ -132,21 +152,18 @@ function DocumentsTreeNode(props: { (doc): doc is NonNullable => doc !== null, ); - const isParentOwnerless = props.doc.docRef.permissions.anyone === "Own"; - // Don't show ownerless children or deleted documents const filtered = loadedChildDocs.filter( (childDoc) => !childDoc.docRef.isDeleted && - (isParentOwnerless || childDoc.docRef.permissions.anyone !== "Own"), + (source.isParentOwnerless || childDoc.docRef.permissions.anyone !== "Own"), ); // Sort by createdAt descending (newest first) - const docs = userState.documents; filtered.sort((a, b) => { - const aInfo = a.docRef.refId ? docs[a.docRef.refId] : undefined; - const bInfo = b.docRef.refId ? docs[b.docRef.refId] : undefined; - return (bInfo?.createdAt ?? 0) - (aInfo?.createdAt ?? 0); + const aCreatedAt = a.docRef.refId ? (source.createdAtByRefId[a.docRef.refId] ?? 0) : 0; + const bCreatedAt = b.docRef.refId ? (source.createdAtByRefId[b.docRef.refId] ?? 0) : 0; + return bCreatedAt - aCreatedAt; }); return filtered; @@ -159,6 +176,8 @@ function DocumentsTreeNode(props: { indent={props.indent} primaryDoc={props.primaryDoc} secondaryDoc={props.secondaryDoc} + primaryPaneFocus={props.primaryPaneFocus} + secondaryPaneFocus={props.secondaryPaneFocus} refetchPrimaryDoc={props.refetchPrimaryDoc} refetchSecondaryDoc={props.refetchSecondaryDoc} /> @@ -169,6 +188,8 @@ function DocumentsTreeNode(props: { indent={props.indent + 1} primaryDoc={props.primaryDoc} secondaryDoc={props.secondaryDoc} + primaryPaneFocus={props.primaryPaneFocus} + secondaryPaneFocus={props.secondaryPaneFocus} refetchPrimaryDoc={props.refetchPrimaryDoc} refetchSecondaryDoc={props.refetchSecondaryDoc} /> @@ -183,6 +204,8 @@ function DocumentsTreeLeaf(props: { indent: number; primaryDoc: LiveDocWithRef; secondaryDoc?: LiveDocWithRef; + primaryPaneFocus: FocusHandle; + secondaryPaneFocus: FocusHandle; refetchPrimaryDoc: () => void; refetchSecondaryDoc: () => void; }) { @@ -193,6 +216,11 @@ function DocumentsTreeLeaf(props: { const clickedRefId = createMemo(() => props.doc.docRef.refId); const primaryRefId = createMemo(() => props.primaryDoc.docRef.refId); const secondaryRefId = createMemo(() => props.secondaryDoc?.docRef.refId); + const isPrimary = () => clickedRefId() === primaryRefId(); + const isSecondary = () => clickedRefId() === secondaryRefId(); + const isFocused = () => + (isPrimary() && props.primaryPaneFocus.hasFocus()) || + (isSecondary() && props.secondaryPaneFocus.hasFocus()); const iconLetters = createMemo(() => { const doc = props.doc.liveDoc.doc; @@ -211,14 +239,17 @@ function DocumentsTreeLeaf(props: { const handleClick = async () => { // If clicking on primary or secondary doc, navigate to just that doc if (clickedRefId() === primaryRefId() || clickedRefId() === secondaryRefId()) { + props.primaryPaneFocus.setFocused(true); navigate(`/${createLinkPart(props.doc)}`); } else { // Otherwise, open it as a side panel or put on the left if it is a parent doc const clickedDoc = props.doc; const parentOfPrimary = await getDocParent(props.primaryDoc.liveDoc.doc, api); if (parentOfPrimary && clickedDoc.docRef.refId === parentOfPrimary.docRef.refId) { + props.primaryPaneFocus.setFocused(true); navigate(`/${createLinkPart(clickedDoc)}/${createLinkPart(props.primaryDoc)}`); } else { + props.secondaryPaneFocus.setFocused(true); navigate(`/${createLinkPart(props.primaryDoc)}/${createLinkPart(clickedDoc)}`); } } @@ -229,7 +260,8 @@ function DocumentsTreeLeaf(props: { onClick={handleClick} class="related-document" classList={{ - active: props.doc.docRef.refId === props.primaryDoc.docRef.refId, + active: isPrimary() || isSecondary(), + focused: isFocused(), }} style={{ "padding-left": `${props.indent * 16}px` }} > diff --git a/packages/frontend/src/page/history_sidebar.tsx b/packages/frontend/src/page/history_sidebar.tsx index 50b1db463..de635b1da 100644 --- a/packages/frontend/src/page/history_sidebar.tsx +++ b/packages/frontend/src/page/history_sidebar.tsx @@ -1,3 +1,4 @@ +import { formatShortcut, primaryModifier } from "catcolab-ui-components"; import { HistoryNavigator } from "catcolab-ui-components"; import type { SnapshotHistory } from "./use_snapshot_history"; @@ -10,8 +11,8 @@ export function HistorySidebar(props: { history: SnapshotHistory }) { onUndo={props.history.onUndo} onRedo={props.history.onRedo} onSelect={props.history.navigate} - undoTooltip="Undo" - redoTooltip="Redo" + undoTooltip={`Undo (${formatShortcut([primaryModifier, "Z"])})`} + redoTooltip={`Redo (${formatShortcut([primaryModifier, "Shift", "Z"])} or ${formatShortcut([primaryModifier, "Y"])})`} /> ); } diff --git a/packages/frontend/src/page/home_page.css b/packages/frontend/src/page/home_page.css index 61ca7a2df..3e0af0ace 100644 --- a/packages/frontend/src/page/home_page.css +++ b/packages/frontend/src/page/home_page.css @@ -65,13 +65,7 @@ .home-nav-button { margin-top: 20px; padding: 10px 20px; - background: var(--color-topos-primary); - border: none; - border-radius: 5px; - font-family: var(--main-font); font-size: 18px; - color: var(--color-background); - cursor: pointer; } .quick-actions { @@ -205,31 +199,5 @@ } .home-nav-button.get-started { - border: 1px solid var(--color-background); - border-radius: 5px; - cursor: pointer; font-weight: bold; - color: var(--color-background); -} - -.home-nav-button.outline { - background: var(--color-background); - color: var(--color-gray-850); - border: 1px solid var(--color-gray-700); - border-radius: 5px; - cursor: pointer; -} - -.home-nav-button.outline:hover:not(:disabled) { - background-color: var(--color-hover-button-light); -} - -.home-nav-button:hover:not(:disabled) { - background-color: var(--color-topos-primary-hover); -} - -/* stylelint-disable-next-line no-descending-specificity -- :disabled and :hover:not(:disabled) are mutually exclusive */ -.home-nav-button:disabled { - opacity: 0.6; - cursor: not-allowed; } diff --git a/packages/frontend/src/page/home_page.tsx b/packages/frontend/src/page/home_page.tsx index a14b6e3e5..75b504d1e 100644 --- a/packages/frontend/src/page/home_page.tsx +++ b/packages/frontend/src/page/home_page.tsx @@ -1,5 +1,6 @@ import { useNavigate } from "@solidjs/router"; import { getAuth } from "firebase/auth"; +import ArrowLeftIcon from "lucide-solid/icons/arrow-left"; import Binoculars from "lucide-solid/icons/binoculars"; import Bird from "lucide-solid/icons/bird"; import ExternalLink from "lucide-solid/icons/external-link"; @@ -11,6 +12,7 @@ import LogInIcon from "lucide-solid/icons/log-in"; import { useAuth, useFirebaseApp } from "solid-firebase"; import { createSignal, Match, Show, Switch } from "solid-js"; +import { Button } from "catcolab-ui-components"; import { useApi } from "../api"; import { createModel } from "../model/document"; import { stdTheories } from "../stdlib"; @@ -57,12 +59,10 @@ export default function HomePage() {
      - +
      @@ -74,28 +74,34 @@ export default function HomePage() {
      - + - + - +
      diff --git a/packages/frontend/src/page/sidebar_layout.css b/packages/frontend/src/page/sidebar_layout.css index a97e7344f..beb0e063b 100644 --- a/packages/frontend/src/page/sidebar_layout.css +++ b/packages/frontend/src/page/sidebar_layout.css @@ -122,7 +122,7 @@ .related-document { padding: 5px 8px; height: 30px; - border-radius: 6px; + border-left: 3px solid transparent; transition: background 20ms; display: flex; justify-content: space-between; @@ -173,6 +173,10 @@ background: var(--color-hover-bg-dark); } } + + &.focused { + border-left-color: var(--color-indicator); + } } .resizeable-handle { diff --git a/packages/frontend/src/stdlib/analyses.tsx b/packages/frontend/src/stdlib/analyses.tsx index 38b19a6a0..2bb74794c 100644 --- a/packages/frontend/src/stdlib/analyses.tsx +++ b/packages/frontend/src/stdlib/analyses.tsx @@ -358,7 +358,7 @@ export function renderSQL( help, component: (props) => , initialContent: () => ({ - backend: SQLBackend.MySQL, + backend: SQLBackend.PostgresSQL, filename: "schema.sql", }), }; diff --git a/packages/frontend/src/stdlib/analyses/sql.module.css b/packages/frontend/src/stdlib/analyses/sql.module.css new file mode 100644 index 000000000..83698f2af --- /dev/null +++ b/packages/frontend/src/stdlib/analyses/sql.module.css @@ -0,0 +1,9 @@ +.headerContainer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + flex-wrap: nowrap; + white-space: nowrap; + flex-direction: row; +} diff --git a/packages/frontend/src/stdlib/analyses/sql.tsx b/packages/frontend/src/stdlib/analyses/sql.tsx index 0ae68af1c..c8c8e1c18 100644 --- a/packages/frontend/src/stdlib/analyses/sql.tsx +++ b/packages/frontend/src/stdlib/analyses/sql.tsx @@ -4,10 +4,12 @@ import Copy from "lucide-solid/icons/copy"; import Download from "lucide-solid/icons/download"; import { For, Match, Show, Switch } from "solid-js"; -import { BlockTitle, ErrorAlert, IconButton } from "catcolab-ui-components"; +import { CodeView, BlockTitle, ErrorAlert, IconButton } from "catcolab-ui-components"; import type { ModelAnalysisProps } from "../../analysis"; import * as SQL from "./sql_types.ts"; +import styles from "./sql.module.css"; + const copyToClipboard = (text: string) => navigator.clipboard.writeText(text); const tooltip = () => ( @@ -31,14 +33,7 @@ const tooltip = () => ( export function SQLHeader(sql: string) { return ( -
      +
      copyToClipboard(sql)} disabled={false} @@ -105,15 +100,20 @@ export default function SQLSchemaAnalysis( actions={SQLHeader(sql())} settingsPane={BackendConfig()} /> -
      {sql()}
      + + +
      )} - -

      {"The model failed to compile into a SQL script."}

      -

      {"Check for cycles in foreign key constraints."}

      -
      +
      + + +

      {"The model failed to compile into a SQL script."}

      +

      {"Check for cycles in foreign key constraints."}

      +
      +
      )} diff --git a/packages/frontend/src/visualization/elk_svg.tsx b/packages/frontend/src/visualization/elk_svg.tsx index e98bf9e21..d5b9a8e2b 100644 --- a/packages/frontend/src/visualization/elk_svg.tsx +++ b/packages/frontend/src/visualization/elk_svg.tsx @@ -40,14 +40,20 @@ export function ElkLayout(props: { () => { const elk = elkResource(); const graph = props.graph; + const args = props.args; + const elkToLayout = props.elkToLayout; if (elk && graph) { - return [elk, graph] as const; + return [elk, graph, args, elkToLayout] as const; } }, - // oxlint-disable-next-line solid/reactivity -- createResource fetcher - async ([elk, graph]: readonly [ELK, ElkNode]): Promise => { - const elkNode = await elk.layout(graph, props.args); - return props.elkToLayout(elkNode); + async ([elk, graph, args, elkToLayout]: readonly [ + ELK, + ElkNode, + ElkLayoutArguments | undefined, + (e: ElkNode) => T, + ]): Promise => { + const elkNode = await elk.layout(graph, args); + return elkToLayout(elkNode); }, ); diff --git a/packages/frontend/src/vite-env.d.ts b/packages/frontend/src/vite-env.d.ts index 93a008f03..419a52433 100644 --- a/packages/frontend/src/vite-env.d.ts +++ b/packages/frontend/src/vite-env.d.ts @@ -11,3 +11,10 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +declare module "*.mdx" { + import type { Component } from "solid-js"; + + const MDXComponent: Component; + export default MDXComponent; +} diff --git a/packages/gaios/package.json b/packages/gaios/package.json index 966261953..97102b6df 100644 --- a/packages/gaios/package.json +++ b/packages/gaios/package.json @@ -39,7 +39,7 @@ "eslint-plugin-solid": "^0.14.5", "nodemon": "^3.1.9", "pushwork": "^1.1.8", - "typescript": "^5.2.2", + "typescript": "^6.0.3", "vite": "^5.3.4", "vite-plugin-css-injected-by-js": "^3.5.2", "vite-plugin-solid": "^2.10.2", diff --git a/packages/gaios/pnpm-lock.yaml b/packages/gaios/pnpm-lock.yaml index 1cf81d4c7..a23fbaa4e 100644 --- a/packages/gaios/pnpm-lock.yaml +++ b/packages/gaios/pnpm-lock.yaml @@ -53,7 +53,7 @@ importers: version: 10.4.20(postcss@8.5.1) eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.39.4)(typescript@5.9.2) + version: 0.14.5(eslint@9.39.4)(typescript@6.0.3) nodemon: specifier: ^3.1.9 version: 3.1.9 @@ -61,8 +61,8 @@ importers: specifier: ^1.1.8 version: 1.1.8 typescript: - specifier: ^5.2.2 - version: 5.9.2 + specifier: ^6.0.3 + version: 6.0.3 vite: specifier: ^5.3.4 version: 5.4.14(@types/node@18.19.75)(lightningcss@1.29.1) @@ -407,12 +407,16 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -577,6 +581,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -645,8 +652,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -711,6 +718,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} @@ -1516,8 +1526,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -1944,7 +1954,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.14.0 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 @@ -1965,13 +1975,18 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -2095,6 +2110,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} + '@types/json-schema@7.0.15': {} '@types/node@18.19.75': @@ -2111,12 +2128,12 @@ snapshots: '@types/uuid@10.0.0': {} - '@typescript-eslint/project-service@8.58.1(typescript@5.9.2)': + '@typescript-eslint/project-service@8.58.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.3) '@typescript-eslint/types': 8.58.1 debug: 4.4.3 - typescript: 5.9.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2125,35 +2142,35 @@ snapshots: '@typescript-eslint/types': 8.58.1 '@typescript-eslint/visitor-keys': 8.58.1 - '@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.3)': dependencies: - typescript: 5.9.2 + typescript: 6.0.3 '@typescript-eslint/types@8.58.1': {} - '@typescript-eslint/typescript-estree@8.58.1(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.58.1(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.2) + '@typescript-eslint/project-service': 8.58.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.3) '@typescript-eslint/types': 8.58.1 '@typescript-eslint/visitor-keys': 8.58.1 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 tinyglobby: 0.2.16 - ts-api-utils: 2.5.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.1(eslint@9.39.4)(typescript@5.9.2)': + '@typescript-eslint/utils@8.58.1(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@typescript-eslint/scope-manager': 8.58.1 '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.3) eslint: 9.39.4 - typescript: 5.9.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2179,7 +2196,7 @@ snapshots: acorn@8.16.0: {} - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -2249,6 +2266,11 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 @@ -2409,16 +2431,16 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@5.9.2): + eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.58.1(eslint@9.39.4)(typescript@5.9.2) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4)(typescript@6.0.3) eslint: 9.39.4 estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 known-css-properties: 0.30.0 style-to-object: 1.0.14 - typescript: 5.9.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2443,11 +2465,11 @@ snapshots: '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.6 - ajv: 6.14.0 + '@types/estree': 1.0.8 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -2736,7 +2758,7 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.14 minimatch@9.0.9: dependencies: @@ -3036,15 +3058,15 @@ snapshots: touch@3.1.1: {} - ts-api-utils@2.5.0(typescript@5.9.2): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 5.9.2 + typescript: 6.0.3 type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript@5.9.2: {} + typescript@6.0.3: {} undefsafe@2.0.5: {} diff --git a/packages/gaios/src/model_tool.tsx b/packages/gaios/src/model_tool.tsx index cc04d9d8c..4d14fcfe0 100644 --- a/packages/gaios/src/model_tool.tsx +++ b/packages/gaios/src/model_tool.tsx @@ -11,6 +11,7 @@ import { ModelNotebookEditor } from "../../frontend/src/model/model_editor"; import { ModelDocumentHead } from "../../frontend/src/model/model_info"; import { stdTheories } from "../../frontend/src/stdlib"; import { TheoryLibraryContext } from "../../frontend/src/theory"; +import { rootFocus } from "../../ui-components/src/util/focus"; import type { ModelDoc } from "./model_datatype"; import "../../ui-components/src/global.css"; @@ -54,7 +55,10 @@ export function renderModelTool(handle: DocHandle, element: ToolElemen - + )} diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 54de9d312..a20951061 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -27,6 +27,7 @@ "@solid-primitives/destructure": "^0.2.1", "katex": "^0.16.22", "lucide-solid": "^0.471.0", + "shiki": "^4.0.2", "solid-js": "^1.9.10" }, "devDependencies": { @@ -46,7 +47,7 @@ "playwright": "^1.56.1", "storybook": "^10.0.2", "storybook-solidjs-vite": "^10.0.5", - "typescript": "^5.2.2", + "typescript": "^6.0.3", "vite": "^7.2.2", "vite-plugin-solid": "^2.11.10", "vitest": "^4.0.8" diff --git a/packages/ui-components/pnpm-lock.yaml b/packages/ui-components/pnpm-lock.yaml index 728992e35..9ba62674b 100644 --- a/packages/ui-components/pnpm-lock.yaml +++ b/packages/ui-components/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: lucide-solid: specifier: ^0.471.0 version: 0.471.0(solid-js@1.9.10) + shiki: + specifier: ^4.0.2 + version: 4.0.2 solid-js: specifier: ^1.9.10 version: 1.9.10 @@ -80,7 +83,7 @@ importers: version: 4.0.8(@vitest/browser@4.0.8(vite@7.2.2(@types/node@24.10.0))(vitest@4.0.8))(vitest@4.0.8) eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.39.4)(typescript@5.9.3) + version: 0.14.5(eslint@9.39.4)(typescript@6.0.3) playwright: specifier: ^1.56.1 version: 1.56.1 @@ -89,10 +92,10 @@ importers: version: 10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)) storybook-solidjs-vite: specifier: ^10.0.5 - version: 10.0.5(@testing-library/jest-dom@6.9.1)(esbuild@0.25.11)(rollup@4.52.5)(solid-js@1.9.10)(storybook@10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)))(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0)) + version: 10.0.5(@testing-library/jest-dom@6.9.1)(esbuild@0.25.11)(rollup@4.52.5)(solid-js@1.9.10)(storybook@10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)))(typescript@6.0.3)(vite@7.2.2(@types/node@24.10.0)) typescript: - specifier: ^5.2.2 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 vite: specifier: ^7.2.2 version: 7.2.2(@types/node@24.10.0) @@ -112,6 +115,10 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} @@ -177,8 +184,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': @@ -444,12 +451,16 @@ packages: '@github/relative-time-element@5.0.0': resolution: {integrity: sha512-L/2r0DNR/rMbmHWcsdmhtOiy2gESoGOhItNFD4zJ3nZfHl79Dx3N18Vfx/pYr2lruMOdk1cJZb4wEumm+Dxm1w==} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -626,6 +637,37 @@ packages: cpu: [x64] os: [win32] + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} + + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} + + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} + + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@solid-primitives/active-element@2.1.3': resolution: {integrity: sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A==} peerDependencies: @@ -770,12 +812,18 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -785,6 +833,9 @@ packages: '@types/react@19.2.2': resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/project-service@8.57.0': resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -822,6 +873,9 @@ packages: resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + '@vitest/browser-playwright@4.0.8': resolution: {integrity: sha512-MUi0msIAPXcA2YAuVMcssrSYP/yylxLt347xyTC6+ODl0c4XQFs0d2AN3Pc3iTa0pxIGmogflUV6eogXpPbJeA==} peerDependencies: @@ -904,8 +958,13 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -977,8 +1036,8 @@ packages: resolution: {integrity: sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==} hasBin: true - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -999,6 +1058,9 @@ packages: caniuse-lite@1.0.30001752: resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -1011,6 +1073,12 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -1034,6 +1102,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -1054,6 +1125,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1074,6 +1148,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1211,8 +1288,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.1: - resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} @@ -1251,6 +1328,12 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} @@ -1261,6 +1344,9 @@ packages: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1410,10 +1496,28 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} engines: {node: '>=12.13'} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1451,6 +1555,12 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1533,6 +1643,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1562,6 +1675,15 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1601,6 +1723,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1651,6 +1777,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1685,6 +1814,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1737,6 +1869,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -1754,14 +1889,29 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -1779,6 +1929,12 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-plugin-solid@2.11.10: resolution: {integrity: sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==} peerDependencies: @@ -1915,6 +2071,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -1925,6 +2084,12 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.5': {} '@babel/core@7.28.5': @@ -2007,7 +2172,7 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.28.4': {} + '@babel/runtime@7.29.2': {} '@babel/template@7.27.2': dependencies: @@ -2194,7 +2359,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.14.0 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 @@ -2228,13 +2393,18 @@ snapshots: '@github/relative-time-element@5.0.0': {} - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -2248,13 +2418,13 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.2(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.2(typescript@6.0.3)(vite@7.2.2(@types/node@24.10.0))': dependencies: glob: 10.4.5 - react-docgen-typescript: 2.4.0(typescript@5.9.3) + react-docgen-typescript: 2.4.0(typescript@6.0.3) vite: 7.2.2(@types/node@24.10.0) optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -2354,6 +2524,46 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true + '@shikijs/core@4.0.2': + dependencies: + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/primitive@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/types@4.0.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@solid-primitives/active-element@2.1.3(solid-js@1.9.10)': dependencies: '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) @@ -2461,8 +2671,8 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.4 + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -2515,10 +2725,18 @@ snapshots: '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} '@types/katex@0.16.7': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/mdx@2.0.13': {} '@types/node@24.10.0': @@ -2527,14 +2745,16 @@ snapshots: '@types/react@19.2.2': dependencies: - csstype: 3.1.3 + csstype: 3.2.3 - '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)': + '@types/unist@3.0.3': {} + + '@typescript-eslint/project-service@8.57.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@6.0.3) '@typescript-eslint/types': 8.57.0 debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2543,35 +2763,35 @@ snapshots: '@typescript-eslint/types': 8.57.0 '@typescript-eslint/visitor-keys': 8.57.0 - '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.57.0(typescript@6.0.3)': dependencies: - typescript: 5.9.3 + typescript: 6.0.3 '@typescript-eslint/types@8.57.0': {} - '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.57.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.57.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@6.0.3) '@typescript-eslint/types': 8.57.0 '@typescript-eslint/visitor-keys': 8.57.0 debug: 4.4.3 minimatch: 10.2.4 semver: 7.7.3 tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.4.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.0(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/utils@8.57.0(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@typescript-eslint/scope-manager': 8.57.0 '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.57.0(typescript@6.0.3) eslint: 9.39.4 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2580,6 +2800,8 @@ snapshots: '@typescript-eslint/types': 8.57.0 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.1': {} + '@vitest/browser-playwright@4.0.8(playwright@1.56.1)(vite@7.2.2(@types/node@24.10.0))(vitest@4.0.8)': dependencies: '@vitest/browser': 4.0.8(vite@7.2.2(@types/node@24.10.0))(vitest@4.0.8) @@ -2698,13 +2920,15 @@ snapshots: '@vitest/pretty-format': 4.0.8 tinyrainbow: 3.0.3 - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 acorn@8.15.0: {} - ajv@6.14.0: + acorn@8.16.0: {} + + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -2767,7 +2991,7 @@ snapshots: baseline-browser-mapping@2.8.22: {} - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -2792,6 +3016,8 @@ snapshots: caniuse-lite@1.0.30001752: {} + ccount@2.0.1: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -2807,6 +3033,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + check-error@2.1.1: {} chromatic@12.2.0: {} @@ -2817,6 +3047,8 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@8.3.0: {} concat-map@0.0.1: {} @@ -2833,6 +3065,8 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2843,6 +3077,10 @@ snapshots: dequal@2.0.3: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -2892,16 +3130,16 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@5.9.3): + eslint-plugin-solid@0.14.5(eslint@9.39.4)(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.57.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4)(typescript@6.0.3) eslint: 9.39.4 estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 known-css-properties: 0.30.0 style-to-object: 1.0.14 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2926,11 +3164,11 @@ snapshots: '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.14.0 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -2957,8 +3195,8 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -3004,10 +3242,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.1 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.4.1: {} + flatted@3.4.2: {} foreground-child@3.3.1: dependencies: @@ -3042,12 +3280,32 @@ snapshots: has-flag@4.0.0: {} + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + html-entities@2.3.3: {} html-escaper@2.0.2: {} html-tags@3.3.1: {} + html-void-elements@3.0.0: {} + ignore@5.3.2: {} import-fresh@3.3.1: @@ -3181,10 +3439,39 @@ snapshots: dependencies: semver: 7.7.3 + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + merge-anything@5.1.7: dependencies: is-what: 4.1.16 + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + min-indent@1.0.1: {} minimatch@10.2.4: @@ -3193,7 +3480,7 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 minimatch@9.0.5: dependencies: @@ -3211,6 +3498,14 @@ snapshots: node-releases@2.0.27: {} + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3288,11 +3583,13 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + property-information@7.1.0: {} + punycode@2.3.1: {} - react-docgen-typescript@2.4.0(typescript@5.9.3): + react-docgen-typescript@2.4.0(typescript@6.0.3): dependencies: - typescript: 5.9.3 + typescript: 6.0.3 react-dom@19.2.0(react@19.2.0): dependencies: @@ -3316,6 +3613,16 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + resolve-from@4.0.0: {} rollup@4.52.5: @@ -3364,6 +3671,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@4.0.2: + dependencies: + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3415,13 +3733,15 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + stackback@0.0.2: {} std-env@3.10.0: {} - storybook-solidjs-vite@10.0.5(@testing-library/jest-dom@6.9.1)(esbuild@0.25.11)(rollup@4.52.5)(solid-js@1.9.10)(storybook@10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)))(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0)): + storybook-solidjs-vite@10.0.5(@testing-library/jest-dom@6.9.1)(esbuild@0.25.11)(rollup@4.52.5)(solid-js@1.9.10)(storybook@10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)))(typescript@6.0.3)(vite@7.2.2(@types/node@24.10.0)): dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.2(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.2(typescript@6.0.3)(vite@7.2.2(@types/node@24.10.0)) '@storybook/builder-vite': 10.0.2(esbuild@0.25.11)(rollup@4.52.5)(storybook@10.0.2(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.2(@types/node@24.10.0)))(vite@7.2.2(@types/node@24.10.0)) '@storybook/global': 5.0.0 solid-js: 1.9.10 @@ -3429,7 +3749,7 @@ snapshots: vite: 7.2.2(@types/node@24.10.0) vite-plugin-solid: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.2.2(@types/node@24.10.0)) optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - '@testing-library/jest-dom' - esbuild @@ -3471,6 +3791,11 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3512,9 +3837,11 @@ snapshots: totalist@3.0.1: {} - ts-api-utils@2.4.0(typescript@5.9.3): + trim-lines@3.0.1: {} + + ts-api-utils@2.4.0(typescript@6.0.3): dependencies: - typescript: 5.9.3 + typescript: 6.0.3 ts-dedent@2.2.0: {} @@ -3524,10 +3851,33 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript@5.9.3: {} + typescript@6.0.3: {} undici-types@7.16.0: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} unplugin@2.3.10: @@ -3547,6 +3897,16 @@ snapshots: dependencies: punycode: 2.3.1 + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.2.2(@types/node@24.10.0)): dependencies: '@babel/core': 7.28.5 @@ -3647,3 +4007,5 @@ snapshots: yallist@3.1.1: {} yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/packages/ui-components/src/button.tsx b/packages/ui-components/src/button.tsx index 80561c110..62f6ef3ef 100644 --- a/packages/ui-components/src/button.tsx +++ b/packages/ui-components/src/button.tsx @@ -12,7 +12,7 @@ export function Button( children: JSX.Element; } & ComponentProps<"button">, ) { - const [props, buttonProps] = splitProps(allProps, ["variant", "children"]); + const [props, buttonProps] = splitProps(allProps, ["variant", "children", "class"]); const variantClass = () => { switch (props.variant) { @@ -29,9 +29,9 @@ export function Button( return ( diff --git a/packages/ui-components/src/code_view.stories.tsx b/packages/ui-components/src/code_view.stories.tsx new file mode 100644 index 000000000..98fd56d3c --- /dev/null +++ b/packages/ui-components/src/code_view.stories.tsx @@ -0,0 +1,91 @@ +import { bundledLanguagesInfo, bundledThemesInfo } from "shiki"; +import { For } from "solid-js"; +import type { Meta, StoryObj } from "storybook-solidjs-vite"; + +import { CodeView } from "./code_view"; + +const langs = bundledLanguagesInfo.map((l) => l.id); +const themes = bundledThemesInfo.map((t) => t.id); + +const meta: Meta = { + title: "Misc/CodeView", + component: CodeView, + tags: ["autodocs"], + argTypes: { + text: { + control: "text", + description: "The source code to display", + }, + lang: { + control: "select", + options: langs, + description: "The lang for syntax highlighting", + }, + theme: { + control: "select", + options: themes, + description: "The theme for syntax highlighting", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const SQL: Story = { + // excluding from autodocs and dev seems to be the way to have this + // component as the first thing in the docs and only there + tags: ["!autodocs", "!dev"], + args: { + lang: "sql", + text: `SELECT p.name, COUNT(o.id) AS order_count +FROM products p +LEFT JOIN orders o ON o.product_id = p.id +WHERE p.active = TRUE +GROUP BY p.name +ORDER BY order_count DESC;`, + }, +}; + +export const Typescript: Story = { + args: { + lang: "typescript", + text: `function greet(name: string): string { + return \`Hello, \${name}!\`; +}`, + }, +}; + +const sampleTs = `function greet(name: string): string { + return \`Hello, \${name}!\`; +}`; + +export const Themes: Story = { + render: () => ( +
      + + {(theme) => ( +
      +

      {theme}

      + +
      + )} +
      +
      + ), +}; diff --git a/packages/ui-components/src/code_view.tsx b/packages/ui-components/src/code_view.tsx new file mode 100644 index 000000000..c1c5fb69c --- /dev/null +++ b/packages/ui-components/src/code_view.tsx @@ -0,0 +1,26 @@ +import { type BundledLanguage, type BundledTheme, codeToHtml } from "shiki"; +import { createResource } from "solid-js"; + +export type CodeViewProps = { + text: string; + lang: BundledLanguage; + theme?: BundledTheme; +}; + +export const CodeView = (props: CodeViewProps) => { + const [html] = createResource( + () => ({ text: props.text, lang: props.lang, theme: props.theme }), + ({ text, lang, theme }) => + codeToHtml(text, { + lang, + theme: theme ?? "min-light", + }), + ); + + return ( + // shiki uses hast-util-to-html which escapes html entities so no need + // for extra sanitization and setting innerHTML should be safe + // oxlint-disable-next-line solid/no-innerhtml +
      + ); +}; diff --git a/packages/ui-components/src/colors.css b/packages/ui-components/src/colors.css index 01847c714..fb8387c83 100644 --- a/packages/ui-components/src/colors.css +++ b/packages/ui-components/src/colors.css @@ -78,12 +78,12 @@ * Positive Colors * From lightest to darkest * ======================================== */ - --color-icon-button-positive-hover: #efe; - --color-icon-button-positive-active: #dfd; - --color-button-positive-hover: #3cb371; - --color-button-positive-base: #2e8b57; - --color-icon-button-positive-text: #0a0; - --color-button-positive-border: darkgreen; + --color-icon-button-positive-hover: #e8f7f6; + --color-icon-button-positive-active: #d4efed; + --color-button-positive-hover: var(--color-topos-primary-hover); + --color-button-positive-base: var(--color-topos-primary); + --color-icon-button-positive-text: var(--color-topos-primary); + --color-button-positive-border: var(--color-topos-primary); /* ======================================== * Danger Colors @@ -111,8 +111,9 @@ /* ======================================== * Information Colors * ======================================== */ - --color-alert-question: cornflowerblue; - --color-alert-note: teal; + --color-indicator: cornflowerblue; + --color-alert-question: var(--color-indicator); + --color-alert-note: var(--color-topos-primary); /* ======================================== * Rich Text Editor Colors diff --git a/packages/ui-components/src/colors.stories.tsx b/packages/ui-components/src/colors.stories.tsx index 10cf413d7..9019b66a0 100644 --- a/packages/ui-components/src/colors.stories.tsx +++ b/packages/ui-components/src/colors.stories.tsx @@ -456,7 +456,7 @@ export const AllColors: Story = {
      - @@ -2635,8 +2620,8 @@ export const PositiveColorUsage: Story = {

      Positive icon buttons use transparent background by default. On hover, they - use --color-icon-button-positive-hover for background and - --color-icon-button-positive-text for color. On active, they use + use Topos-primary-tinted --color-icon-button-positive-hover for background + and --color-icon-button-positive-text for color. On active, they use --color-icon-button-positive-active.

      - Alert Question Variant (alert.css) + Indicator (alert.css, history_navigator.module.css)

      - Question alerts use --color-alert-question (cornflowerblue) for the accent - color and heading text. + Indicators use --color-indicator (cornflowerblue), with + --color-alert-question as the alert-specific alias for question headings.

      @@ -3189,8 +3174,8 @@ export const InformationColorUsage: Story = { Alert Note Variant (alert.css)

      - Note alerts use --color-alert-note (teal) for the accent color and heading - text. + Note alerts use --color-alert-note (Topos primary) for the accent color and + heading text.

      @@ -3231,6 +3216,7 @@ export const InformationColorUsage: Story = { gap: "1.5rem", }} > +

      diff --git a/packages/ui-components/src/expandable_list.stories.tsx b/packages/ui-components/src/expandable_list.stories.tsx index dacb06fbb..651447ce6 100644 --- a/packages/ui-components/src/expandable_list.stories.tsx +++ b/packages/ui-components/src/expandable_list.stories.tsx @@ -179,7 +179,9 @@ export const WithKatex: Story = {
      } + renderItem={(item) => + typeof item === "string" ? : item + } />
      ); diff --git a/packages/ui-components/src/foldable.stories.tsx b/packages/ui-components/src/foldable.stories.tsx index 48ef5a003..5759f8623 100644 --- a/packages/ui-components/src/foldable.stories.tsx +++ b/packages/ui-components/src/foldable.stories.tsx @@ -7,7 +7,7 @@ import { Foldable } from "./foldable"; import { IconButton } from "./icon_button"; const meta = { - title: "Foldable", + title: "Layout/Foldable", component: Foldable, } satisfies Meta; diff --git a/packages/ui-components/src/history_navigator.module.css b/packages/ui-components/src/history_navigator.module.css index 71197aa97..ee5f1b1b7 100644 --- a/packages/ui-components/src/history_navigator.module.css +++ b/packages/ui-components/src/history_navigator.module.css @@ -58,7 +58,7 @@ width: 10px; height: 10px; border-radius: 50%; - background-color: var(--color-alert-question); + background-color: var(--color-indicator); } .timeCell { diff --git a/packages/ui-components/src/index.ts b/packages/ui-components/src/index.ts index de93d87b7..cbff9ec8a 100644 --- a/packages/ui-components/src/index.ts +++ b/packages/ui-components/src/index.ts @@ -2,6 +2,7 @@ export * from "./alert"; export * from "./block_title"; export * from "./settings_disclosure"; export * from "./button"; +export * from "./code_view"; export * from "./completions"; export * from "./dialog"; export * from "./document_type_icon"; @@ -22,6 +23,7 @@ export * from "./relative_time"; export * from "./resizable"; export * from "./spinner"; export * from "./text_input"; +export * from "./util/focus"; export * from "./util/keyboard"; export * from "./virtual_list"; export * from "./warning_banner"; diff --git a/packages/ui-components/src/panel.css b/packages/ui-components/src/panel.css index d166b0998..2ec4c8a7f 100644 --- a/packages/ui-components/src/panel.css +++ b/packages/ui-components/src/panel.css @@ -3,6 +3,7 @@ flex-direction: row; align-items: center; width: 100%; + gap: 4px; & .filler { flex: 1; diff --git a/packages/ui-components/src/relative_time.tsx b/packages/ui-components/src/relative_time.tsx index 672c01ac2..b0f8e0d31 100644 --- a/packages/ui-components/src/relative_time.tsx +++ b/packages/ui-components/src/relative_time.tsx @@ -1,3 +1,5 @@ +import { Show } from "solid-js"; + // oxlint-disable-next-line no-unassigned-import -- side-effect import registers the custom element import "@github/relative-time-element"; @@ -19,9 +21,34 @@ declare module "solid-js" { } } +const warnedTimestamps = new Set(); + +function toIsoStringSafe(ts: unknown): string | undefined { + if (typeof ts !== "number" || !Number.isFinite(ts)) { + if (!warnedTimestamps.has(ts)) { + warnedTimestamps.add(ts); + console.warn("RelativeTime: invalid timestamp", ts); + } + return undefined; + } + const d = new Date(ts); + if (Number.isNaN(d.getTime())) { + if (!warnedTimestamps.has(ts)) { + warnedTimestamps.add(ts); + console.warn("RelativeTime: timestamp produced invalid Date", ts); + } + return undefined; + } + return d.toISOString(); +} + /** Display a timestamp as relative text, with the exact time as a tooltip. Auto-updates. */ export function RelativeTime(props: { timestamp: number }) { - const datetime = () => new Date(props.timestamp).toISOString(); + const datetime = () => toIsoStringSafe(props.timestamp); - return ; + return ( + + ); } diff --git a/packages/ui-components/src/text_input.tsx b/packages/ui-components/src/text_input.tsx index 0d70d21cf..c3aeaef2b 100644 --- a/packages/ui-components/src/text_input.tsx +++ b/packages/ui-components/src/text_input.tsx @@ -5,6 +5,7 @@ import { type ComponentProps, createEffect, createSignal, type JSX, splitProps } void focus; import { type Completion, Completions, type CompletionsRef } from "./completions"; +import type { FocusHandle } from "./util/focus"; import { assertTypelevel } from "./util/types"; /** Props for `TextInput` component. */ @@ -16,6 +17,9 @@ type TextInputProps = Omit, "onKeyDown"> & /** Optional props available to a `TextInput` component. */ export type TextInputOptions = TextInputActions & { + /** Focus state for this input. */ + focus?: FocusHandle; + /** Whether the input is active: allowed to the grab the focus. */ isActive?: boolean; @@ -103,6 +107,7 @@ type TextInputActions = { // XXX: Need the list of options as a *value* to split props. const TEXT_INPUT_OPTIONS = [ + "focus", "isActive", "hasFocused", "hasBlurred", @@ -143,7 +148,7 @@ export function TextInput(allProps: TextInputProps) { let ref!: HTMLInputElement; createEffect(() => { - if (options.isActive && document.activeElement !== ref) { + if ((options.focus?.hasFocus() ?? options.isActive) && document.activeElement !== ref) { ref.focus(); // Move cursor to end of input. ref.selectionStart = ref.selectionEnd = ref.value.length; @@ -156,7 +161,10 @@ export function TextInput(allProps: TextInputProps) { const onKeyDown: JSX.EventHandler = (evt) => { const remaining = completionsRef()?.remainingCompletions() ?? []; const value = evt.currentTarget.value; - if (options.interceptKeyDown?.(evt)) { + if (evt.key === "Escape" && isCompletionsOpen()) { + setCompletionsOpen(false); + evt.stopPropagation(); + } else if (options.interceptKeyDown?.(evt)) { } else if (options.deleteBackward && evt.key === "Backspace" && !value) { options.deleteBackward(); } else if (options.deleteForward && evt.key === "Delete" && !value) { @@ -224,6 +232,7 @@ export function TextInput(allProps: TextInputProps) { value={props.text} use:focus={(isFocused) => { if (isFocused) { + options.focus?.setFocused(true); options.hasFocused?.(); if ( options.completions != null && diff --git a/packages/ui-components/src/util/focus.ts b/packages/ui-components/src/util/focus.ts new file mode 100644 index 000000000..556a5c6c3 --- /dev/null +++ b/packages/ui-components/src/util/focus.ts @@ -0,0 +1,45 @@ +import { createEffect, createSignal, type Accessor } from "solid-js"; + +/** Focus state passed down a component tree. */ +export type FocusHandle = { + hasFocus: Accessor; + setFocused: (focused: boolean) => void; +}; + +/** Root focus handle for a tree that should always remember its last focus. */ +export const rootFocus: FocusHandle = { + hasFocus: () => true, + setFocused: () => {}, +}; + +/** Track which immediate child of a focused parent has focus. */ +export function useChildFocus( + parent: FocusHandle, + options?: { default?: K }, +): { + activeChild: Accessor; + setActiveChild: (child: K | null) => void; + childFocus: (child: K) => FocusHandle; +} { + const [activeChild, setActiveChild] = createSignal(options?.default ?? null); + + createEffect(() => { + if (!parent.hasFocus()) { + setActiveChild(() => options?.default ?? null); + } + }); + + const childFocus = (child: K): FocusHandle => ({ + hasFocus: () => parent.hasFocus() && activeChild() === child, + setFocused: (focused) => { + if (focused) { + setActiveChild(() => child); + parent.setFocused(true); + } else if (activeChild() === child) { + setActiveChild(() => options?.default ?? null); + } + }, + }); + + return { activeChild, setActiveChild, childFocus }; +} diff --git a/packages/ui-components/src/util/keyboard.ts b/packages/ui-components/src/util/keyboard.ts index 0f44b3807..23762a188 100644 --- a/packages/ui-components/src/util/keyboard.ts +++ b/packages/ui-components/src/util/keyboard.ts @@ -9,6 +9,23 @@ The types `ModifierKey` and `KbdKey` are borrowed from export type ModifierKey = "Alt" | "Control" | "Meta" | "Shift"; export type KbdKey = ModifierKey | (string & {}); +/** Platform-appropriate primary modifier key for editor shortcuts. + +Uses Meta (Cmd) on Mac and Control elsewhere, matching native app convention. + */ +export const primaryModifier: ModifierKey = navigator.userAgent.includes("Mac") + ? "Meta" + : "Control"; + +/** Platform-appropriate secondary modifier key for editor shortcuts. + +Uses Control on Mac, where Alt/Option remaps keys, and Alt elsewhere, where +Control tends to already be bound. + */ +export const secondaryModifier: ModifierKey = navigator.userAgent.includes("Mac") + ? "Control" + : "Alt"; + /** Returns whether the modifier key is active in the keyboard event. */ export function keyEventHasModifier(evt: KeyboardEvent, key: ModifierKey): boolean { switch (key) { @@ -24,3 +41,36 @@ export function keyEventHasModifier(evt: KeyboardEvent, key: ModifierKey): boole throw new Error(`Key is not a modifier: ${key}`); } } + +/** Format a modifier key for display to the user (e.g. "Cmd" on Mac, "Ctrl" elsewhere). */ +function formatModifierKey(key: ModifierKey, isMac: boolean): string { + switch (key) { + case "Meta": + return isMac ? "Cmd" : "Win"; + case "Control": + return isMac ? "⌃" : "Ctrl"; + case "Shift": + return "Shift"; + case "Alt": + return isMac ? "⌥" : "Alt"; + } +} + +/** Format a keyboard shortcut for display to the user. + * + * Takes an array of keys (modifiers followed by the main key) and returns + * a human-readable string like "Cmd+Z" or "Ctrl+Shift+Z". + */ +export function formatShortcut(keys: KbdKey[]): string { + if (keys.length === 0) { + return ""; + } + const isMac = navigator.userAgent.includes("Mac"); + const parts = keys.map((key) => { + if (key === "Meta" || key === "Control" || key === "Alt" || key === "Shift") { + return formatModifierKey(key as ModifierKey, isMac); + } + return key; + }); + return parts.join("+"); +} diff --git a/packages/ui-components/src/util/types.ts b/packages/ui-components/src/util/types.ts index 829da28ac..0b105fb42 100644 --- a/packages/ui-components/src/util/types.ts +++ b/packages/ui-components/src/util/types.ts @@ -4,4 +4,5 @@ Call this function as no effect. The minimal version of: */ +// oxlint-disable-next-line typescript-eslint/no-unnecessary-type-parameters -- compile-time assertion helper export function assertTypelevel<_T extends true>() {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32d941a73..d3b51e6c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,17 +15,17 @@ importers: specifier: ^0.37.0 version: 0.37.0 oxlint: - specifier: ^1.51.0 - version: 1.52.0(oxlint-tsgolint@0.16.0) + specifier: ^1.62.0 + version: 1.62.0(oxlint-tsgolint@0.22.1) oxlint-tsgolint: - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.22.1 + version: 0.22.1 stylelint: - specifier: ^17.4.0 - version: 17.4.0(typescript@5.6.2) + specifier: ^17.9.1 + version: 17.9.1(typescript@5.6.2) stylelint-config-standard: specifier: ^40.0.0 - version: 40.0.0(stylelint@17.4.0(typescript@5.6.2)) + version: 40.0.0(stylelint@17.9.1(typescript@5.6.2)) tsx: specifier: ^4.16.5 version: 4.19.1 @@ -75,11 +75,11 @@ packages: '@cacheable/memory@2.0.8': resolution: {integrity: sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==} - '@cacheable/utils@2.4.0': - resolution: {integrity: sha512-PeMMsqjVq+bF0WBsxFBxr/WozBJiZKY0rUojuaCoIaKnEl3Ju1wfEwS+SV1DU/cSe8fqHIPiYJFif8T3MVt4cQ==} + '@cacheable/utils@2.4.1': + resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} - '@csstools/css-calc@3.1.1': - resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -91,8 +91,13 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.0': - resolution: {integrity: sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==} + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} @@ -422,154 +427,154 @@ packages: cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.16.0': - resolution: {integrity: sha512-WQt5lGwRPJBw7q2KNR0mSPDAaMmZmVvDlEEti96xLO7ONhyomQc6fBZxxwZ4qTFedjJnrHX94sFelZ4OKzS7UQ==} + '@oxlint-tsgolint/darwin-arm64@0.22.1': + resolution: {integrity: sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.16.0': - resolution: {integrity: sha512-VJo29XOzdkalvCTiE2v6FU3qZlgHaM8x8hUEVJGPU2i5W+FlocPpmn00+Ld2n7Q0pqIjyD5EyvZ5UmoIEJMfqg==} + '@oxlint-tsgolint/darwin-x64@0.22.1': + resolution: {integrity: sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.16.0': - resolution: {integrity: sha512-MPfqRt1+XRHv9oHomcBMQ3KpTE+CSkZz14wUxDQoqTNdUlV0HWdzwIE9q65I3D9YyxEnqpM7j4qtDQ3apqVvbQ==} + '@oxlint-tsgolint/linux-arm64@0.22.1': + resolution: {integrity: sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.16.0': - resolution: {integrity: sha512-XQSwVUsnwLokMhe1TD6IjgvW5WMTPzOGGkdFDtXWQmlN2YeTw94s/NN0KgDrn2agM1WIgAenEkvnm0u7NgwEyw==} + '@oxlint-tsgolint/linux-x64@0.22.1': + resolution: {integrity: sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.16.0': - resolution: {integrity: sha512-EWdlspQiiFGsP2AiCYdhg5dTYyAlj6y1nRyNI2dQWq4Q/LITFHiSRVPe+7m7K7lcsZCEz2icN/bCeSkZaORqIg==} + '@oxlint-tsgolint/win32-arm64@0.22.1': + resolution: {integrity: sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.16.0': - resolution: {integrity: sha512-1ufk8cgktXJuJZHKF63zCHAkaLMwZrEXnZ89H2y6NO85PtOXqu4zbdNl0VBpPP3fCUuUBu9RvNqMFiv0VsbXWA==} + '@oxlint-tsgolint/win32-x64@0.22.1': + resolution: {integrity: sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.52.0': - resolution: {integrity: sha512-fW2pmR1VzFEdcvOYeSiv+R7CqffOjr9Bv5QmZaHuHJ4ZCqouaF6o48N/hJ3H1n9Zd8PCMFgJkeqUvUsVce01mw==} + '@oxlint/binding-android-arm-eabi@1.62.0': + resolution: {integrity: sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.52.0': - resolution: {integrity: sha512-ptuJljIB+klNi8//qxXyGD51NLJXY9lv40Olc7l3/pEyjejWwXGvGMO0GM6f0JsjmbnDL+VkX7RVQNhByaX8WA==} + '@oxlint/binding-android-arm64@1.62.0': + resolution: {integrity: sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.52.0': - resolution: {integrity: sha512-5d079Uw43BHVZzOwm3uJI2PgSbsZJTpfHDq2jMOR6rRjGiEBlgasaEvAA26VBqpkO1++/59ZCKLBnEpkro3zIg==} + '@oxlint/binding-darwin-arm64@1.62.0': + resolution: {integrity: sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.52.0': - resolution: {integrity: sha512-vRTjnhPEHAyfUhO9w6GM1VkxeVXFcDs+huyB5YNMw+Py+6PRYDFFrrOEr0rZYcoGtSH25ScozZV8I1UXrzaDjQ==} + '@oxlint/binding-darwin-x64@1.62.0': + resolution: {integrity: sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.52.0': - resolution: {integrity: sha512-vFthhhciRAliAjoKMsvi7UkkQp/EtMNhmCRYBuKsNiTH0k4H3SFfbuWWr80Q7+uTXijfBP91KO/EeF48RggC7A==} + '@oxlint/binding-freebsd-x64@1.62.0': + resolution: {integrity: sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.52.0': - resolution: {integrity: sha512-qX3K4mKbju54ojUa8nigVxxZAUDBGu5MGzpoXvWmiw+7hafoQKaLAoTm94EqRlv9v27p864GQBgc4g3qYtMXXA==} + '@oxlint/binding-linux-arm-gnueabihf@1.62.0': + resolution: {integrity: sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.52.0': - resolution: {integrity: sha512-x5D5/EUS9U4kndPncLB6mDfCsv7i8XcRLu0DZyTngXvyqapc96WwmyyOG2j8Dt26aE8Ykgh6AhsHp9bQtoBUAw==} + '@oxlint/binding-linux-arm-musleabihf@1.62.0': + resolution: {integrity: sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.52.0': - resolution: {integrity: sha512-2Ep1tnGLuGG7lUkKG/nilIJ0/T2rebEcATxMJ7afuhD6Z2Sc9dDcpX00IngAMyR9l6hXrvaOw9YA5HUAJVSENg==} + '@oxlint/binding-linux-arm64-gnu@1.62.0': + resolution: {integrity: sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.52.0': - resolution: {integrity: sha512-54wxvb1Pztz0GMgTLUG9HsH8uhZSL4UbG7n4PDxWIRT9TygTVYKfD6D7iasYdKg6ZpWB5Y86VMxgjSJpR/Y7bQ==} + '@oxlint/binding-linux-arm64-musl@1.62.0': + resolution: {integrity: sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.52.0': - resolution: {integrity: sha512-A82Zks1lJyLclrj8n2tJPHOw2ieZXCaBctnCarS1BRlPQMC1Y98vWCLqgvg9ssWy5ZAja0IjUHN1cYsp53mrqA==} + '@oxlint/binding-linux-ppc64-gnu@1.62.0': + resolution: {integrity: sha512-NjQ7K7tpTPDe9J+yq8p/s/J0E7lRCkK2uDBDqvT4XIT6f4Z0tlnr59OBg/WcrmVHER1AbrcfyxhGTXgcG8ytWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.52.0': - resolution: {integrity: sha512-ci89Ou+u9vnA0r4eQqGm/KPEkpea+QEtZCLKkrOAD/K5ZBwjS8ToID6aMgsDbIOJUNBGufsmX0iCC7EWrNKQFA==} + '@oxlint/binding-linux-riscv64-gnu@1.62.0': + resolution: {integrity: sha512-oKZed9gmSwze29dEt3/Wnsv6l/Ygw/FUst+8Kfpv2SGeS/glEoTGZAMQw37SVyzFV76UTHJN2snGgxK2t2+8ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.52.0': - resolution: {integrity: sha512-3/+DVDWajFSu69TaYnKkoUgMEcHR3puO8TcBu3fPCKRhbLjgwDiYIVRdvQX0QaSjkNPJARmpYq7vlPHWNo2cUA==} + '@oxlint/binding-linux-riscv64-musl@1.62.0': + resolution: {integrity: sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.52.0': - resolution: {integrity: sha512-BU7CbceOh00NDmY1IYr72qZoj4sJVHB9DCL2tIq2vyNllNJIpZWTxqlzdqmC4FViXWMy8kZNkOa+SdauH+EcoQ==} + '@oxlint/binding-linux-s390x-gnu@1.62.0': + resolution: {integrity: sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.52.0': - resolution: {integrity: sha512-JUVZ6TKYl1yArS3xGsNLQlZxgVpjNKtZFja6VxSTDy2ToN7H58PiDRcxWoN2XoIcWlHSvK7pkIPFNOyzdEJ23A==} + '@oxlint/binding-linux-x64-gnu@1.62.0': + resolution: {integrity: sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.52.0': - resolution: {integrity: sha512-IatLKG6UUbIbTBjBZ9SIAYp4SIvOpYIXPXn9cMLqWxh9HrHsu0fLNL+VQ67y4vdlIleYLeuIHkAp3M6saIN1RQ==} + '@oxlint/binding-linux-x64-musl@1.62.0': + resolution: {integrity: sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.52.0': - resolution: {integrity: sha512-CWgJ6FepHryuc/lgQWStFf3lcvEkbFLSa9zqO0D0QLVfrdg43I4XItKpL/bnfm4n7obzwgG8j8sBggdoxJQKfw==} + '@oxlint/binding-openharmony-arm64@1.62.0': + resolution: {integrity: sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.52.0': - resolution: {integrity: sha512-EuNAbPpctu8jYMZnvYh53Xw3YVY2nIi9bQlyMjY0eKiJxDv8ikHrAfcVcwTQW9xa5tp0eiMkmW7iHPP5CYUC9Q==} + '@oxlint/binding-win32-arm64-msvc@1.62.0': + resolution: {integrity: sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.52.0': - resolution: {integrity: sha512-wu3fquQttzSXwyy8DfdOG3Kyb17yAbRhwPlly7NHSXkrffAEAmZ6+o38tCNgsReGLugbn/wbq4uS4nEQubCq+A==} + '@oxlint/binding-win32-ia32-msvc@1.62.0': + resolution: {integrity: sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.52.0': - resolution: {integrity: sha512-wikx9I9J9/lPOZlrCCNgm8YjWkia8NZfhWd1TTvZTMguyChbw/oA2VEM6Fzx+kkpA+1qu5Mo7nrLdOXEJavw8g==} + '@oxlint/binding-win32-x64-msvc@1.62.0': + resolution: {integrity: sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -626,8 +631,8 @@ packages: '@vue/shared@3.5.10': resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -680,8 +685,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - cacheable@2.3.3: - resolution: {integrity: sha512-iffYMX4zxKp54evOH27fm92hs+DeC1DhXmNVN8Tr94M/iZIV42dqTHSR2Ik4TOSPyOAwKr7Yu3rN9ALoLkbWyQ==} + cacheable@2.3.4: + resolution: {integrity: sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==} callsite@1.0.0: resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} @@ -862,11 +867,11 @@ packages: resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} engines: {node: '>= 10.13.0'} - flat-cache@6.1.20: - resolution: {integrity: sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==} + flat-cache@6.1.22: + resolution: {integrity: sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==} - flatted@3.4.1: - resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -911,8 +916,8 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globby@16.1.1: - resolution: {integrity: sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==} + globby@16.2.0: + resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} engines: {node: '>=20'} globjoin@0.1.4: @@ -926,8 +931,8 @@ packages: resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} engines: {node: '>=12'} - hashery@1.5.0: - resolution: {integrity: sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==} + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} hasown@2.0.2: @@ -947,6 +952,9 @@ packages: hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + html-tags@5.1.0: resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==} engines: {node: '>=20.10'} @@ -966,13 +974,13 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -1127,8 +1135,8 @@ packages: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1149,16 +1157,16 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.16.0: - resolution: {integrity: sha512-4RuJK2jP08XwqtUu+5yhCbxEauCm6tv2MFHKEMsjbosK2+vy5us82oI3VLuHwbNyZG7ekZA26U2LLHnGR4frIA==} + oxlint-tsgolint@0.22.1: + resolution: {integrity: sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==} hasBin: true - oxlint@1.52.0: - resolution: {integrity: sha512-InLldD+6+3iHJGIrtU1W37UIpsg+xoGCemkZCuSQhxUO3evMX+L872ONvbECyRza9k7ScMCukJIK3Al/2ZMDnQ==} + oxlint@1.62.0: + resolution: {integrity: sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.15.0' + oxlint-tsgolint: '>=0.18.0' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -1212,8 +1220,8 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + postcss@8.5.13: + resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==} engines: {node: ^10 || ^12 || >=14} property-information@6.5.0: @@ -1223,8 +1231,8 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} - qified@0.6.0: - resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} + qified@0.9.1: + resolution: {integrity: sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==} engines: {node: '>=20'} queue-microtask@1.2.3: @@ -1311,8 +1319,8 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@8.2.0: - resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} stringify-entities@4.0.4: @@ -1338,8 +1346,8 @@ packages: peerDependencies: stylelint: ^17.0.0 - stylelint@17.4.0: - resolution: {integrity: sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==} + stylelint@17.9.1: + resolution: {integrity: sha512-THTmnAPJTrg/JhkTWZlSyrO+HUYMx6ELthIHeMyD2WOKqXIJUFQv2Yxn91bvUrZdbBJaW2dUuQdPST2wcQ6C3g==} engines: {node: '>=20.19.0'} hasBin: true @@ -1520,17 +1528,17 @@ snapshots: '@cacheable/memory@2.0.8': dependencies: - '@cacheable/utils': 2.4.0 + '@cacheable/utils': 2.4.1 '@keyv/bigmap': 1.3.1(keyv@5.6.0) hookified: 1.15.1 keyv: 5.6.0 - '@cacheable/utils@2.4.0': + '@cacheable/utils@2.4.1': dependencies: - hashery: 1.5.0 + hashery: 1.5.1 keyv: 5.6.0 - '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -1539,7 +1547,9 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.0': {} + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 '@csstools/css-tokenizer@4.0.0': {} @@ -1647,7 +1657,7 @@ snapshots: '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: - hashery: 1.5.0 + hashery: 1.5.1 hookified: 1.15.1 keyv: 5.6.0 @@ -1722,79 +1732,79 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.37.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.16.0': + '@oxlint-tsgolint/darwin-arm64@0.22.1': optional: true - '@oxlint-tsgolint/darwin-x64@0.16.0': + '@oxlint-tsgolint/darwin-x64@0.22.1': optional: true - '@oxlint-tsgolint/linux-arm64@0.16.0': + '@oxlint-tsgolint/linux-arm64@0.22.1': optional: true - '@oxlint-tsgolint/linux-x64@0.16.0': + '@oxlint-tsgolint/linux-x64@0.22.1': optional: true - '@oxlint-tsgolint/win32-arm64@0.16.0': + '@oxlint-tsgolint/win32-arm64@0.22.1': optional: true - '@oxlint-tsgolint/win32-x64@0.16.0': + '@oxlint-tsgolint/win32-x64@0.22.1': optional: true - '@oxlint/binding-android-arm-eabi@1.52.0': + '@oxlint/binding-android-arm-eabi@1.62.0': optional: true - '@oxlint/binding-android-arm64@1.52.0': + '@oxlint/binding-android-arm64@1.62.0': optional: true - '@oxlint/binding-darwin-arm64@1.52.0': + '@oxlint/binding-darwin-arm64@1.62.0': optional: true - '@oxlint/binding-darwin-x64@1.52.0': + '@oxlint/binding-darwin-x64@1.62.0': optional: true - '@oxlint/binding-freebsd-x64@1.52.0': + '@oxlint/binding-freebsd-x64@1.62.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.52.0': + '@oxlint/binding-linux-arm-gnueabihf@1.62.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.52.0': + '@oxlint/binding-linux-arm-musleabihf@1.62.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.52.0': + '@oxlint/binding-linux-arm64-gnu@1.62.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.52.0': + '@oxlint/binding-linux-arm64-musl@1.62.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.52.0': + '@oxlint/binding-linux-ppc64-gnu@1.62.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.52.0': + '@oxlint/binding-linux-riscv64-gnu@1.62.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.52.0': + '@oxlint/binding-linux-riscv64-musl@1.62.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.52.0': + '@oxlint/binding-linux-s390x-gnu@1.62.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.52.0': + '@oxlint/binding-linux-x64-gnu@1.62.0': optional: true - '@oxlint/binding-linux-x64-musl@1.52.0': + '@oxlint/binding-linux-x64-musl@1.62.0': optional: true - '@oxlint/binding-openharmony-arm64@1.52.0': + '@oxlint/binding-openharmony-arm64@1.62.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.52.0': + '@oxlint/binding-win32-arm64-msvc@1.62.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.52.0': + '@oxlint/binding-win32-ia32-msvc@1.62.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.52.0': + '@oxlint/binding-win32-x64-msvc@1.62.0': optional: true '@shikijs/core@1.21.0': @@ -1874,7 +1884,7 @@ snapshots: '@vue/shared@3.5.10': {} - ajv@8.18.0: + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -1922,13 +1932,13 @@ snapshots: dependencies: fill-range: 7.1.1 - cacheable@2.3.3: + cacheable@2.3.4: dependencies: '@cacheable/memory': 2.0.8 - '@cacheable/utils': 2.4.0 + '@cacheable/utils': 2.4.1 hookified: 1.15.1 keyv: 5.6.0 - qified: 0.6.0 + qified: 0.9.1 callsite@1.0.0: {} @@ -1983,7 +1993,7 @@ snapshots: cosmiconfig@9.0.1(typescript@5.6.2): dependencies: env-paths: 2.2.1 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: @@ -2113,7 +2123,7 @@ snapshots: file-entry-cache@11.1.2: dependencies: - flat-cache: 6.1.20 + flat-cache: 6.1.22 fill-range@7.1.1: dependencies: @@ -2126,13 +2136,13 @@ snapshots: micromatch: 4.0.8 resolve-dir: 1.0.1 - flat-cache@6.1.20: + flat-cache@6.1.22: dependencies: - cacheable: 2.3.3 - flatted: 3.4.1 + cacheable: 2.3.4 + flatted: 3.4.2 hookified: 1.15.1 - flatted@3.4.1: {} + flatted@3.4.2: {} fsevents@2.3.3: optional: true @@ -2177,7 +2187,7 @@ snapshots: globals@11.12.0: {} - globby@16.1.1: + globby@16.2.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 @@ -2192,7 +2202,7 @@ snapshots: has-flag@5.0.1: {} - hashery@1.5.0: + hashery@1.5.1: dependencies: hookified: 1.15.1 @@ -2224,6 +2234,8 @@ snapshots: hookified@1.15.1: {} + hookified@2.2.0: {} + html-tags@5.1.0: {} html-void-elements@3.0.0: {} @@ -2237,9 +2249,12 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.2.0: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 - imurmurhash@0.1.4: {} + import-meta-resolve@4.2.0: {} ini@1.3.8: {} @@ -2383,7 +2398,7 @@ snapshots: arrify: 2.0.1 minimatch: 3.1.2 - nanoid@3.3.11: {} + nanoid@3.3.12: {} nanoid@3.3.7: {} @@ -2417,37 +2432,37 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.37.0 '@oxfmt/binding-win32-x64-msvc': 0.37.0 - oxlint-tsgolint@0.16.0: + oxlint-tsgolint@0.22.1: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.16.0 - '@oxlint-tsgolint/darwin-x64': 0.16.0 - '@oxlint-tsgolint/linux-arm64': 0.16.0 - '@oxlint-tsgolint/linux-x64': 0.16.0 - '@oxlint-tsgolint/win32-arm64': 0.16.0 - '@oxlint-tsgolint/win32-x64': 0.16.0 - - oxlint@1.52.0(oxlint-tsgolint@0.16.0): + '@oxlint-tsgolint/darwin-arm64': 0.22.1 + '@oxlint-tsgolint/darwin-x64': 0.22.1 + '@oxlint-tsgolint/linux-arm64': 0.22.1 + '@oxlint-tsgolint/linux-x64': 0.22.1 + '@oxlint-tsgolint/win32-arm64': 0.22.1 + '@oxlint-tsgolint/win32-x64': 0.22.1 + + oxlint@1.62.0(oxlint-tsgolint@0.22.1): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.52.0 - '@oxlint/binding-android-arm64': 1.52.0 - '@oxlint/binding-darwin-arm64': 1.52.0 - '@oxlint/binding-darwin-x64': 1.52.0 - '@oxlint/binding-freebsd-x64': 1.52.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.52.0 - '@oxlint/binding-linux-arm-musleabihf': 1.52.0 - '@oxlint/binding-linux-arm64-gnu': 1.52.0 - '@oxlint/binding-linux-arm64-musl': 1.52.0 - '@oxlint/binding-linux-ppc64-gnu': 1.52.0 - '@oxlint/binding-linux-riscv64-gnu': 1.52.0 - '@oxlint/binding-linux-riscv64-musl': 1.52.0 - '@oxlint/binding-linux-s390x-gnu': 1.52.0 - '@oxlint/binding-linux-x64-gnu': 1.52.0 - '@oxlint/binding-linux-x64-musl': 1.52.0 - '@oxlint/binding-openharmony-arm64': 1.52.0 - '@oxlint/binding-win32-arm64-msvc': 1.52.0 - '@oxlint/binding-win32-ia32-msvc': 1.52.0 - '@oxlint/binding-win32-x64-msvc': 1.52.0 - oxlint-tsgolint: 0.16.0 + '@oxlint/binding-android-arm-eabi': 1.62.0 + '@oxlint/binding-android-arm64': 1.62.0 + '@oxlint/binding-darwin-arm64': 1.62.0 + '@oxlint/binding-darwin-x64': 1.62.0 + '@oxlint/binding-freebsd-x64': 1.62.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.62.0 + '@oxlint/binding-linux-arm-musleabihf': 1.62.0 + '@oxlint/binding-linux-arm64-gnu': 1.62.0 + '@oxlint/binding-linux-arm64-musl': 1.62.0 + '@oxlint/binding-linux-ppc64-gnu': 1.62.0 + '@oxlint/binding-linux-riscv64-gnu': 1.62.0 + '@oxlint/binding-linux-riscv64-musl': 1.62.0 + '@oxlint/binding-linux-s390x-gnu': 1.62.0 + '@oxlint/binding-linux-x64-gnu': 1.62.0 + '@oxlint/binding-linux-x64-musl': 1.62.0 + '@oxlint/binding-openharmony-arm64': 1.62.0 + '@oxlint/binding-win32-arm64-msvc': 1.62.0 + '@oxlint/binding-win32-ia32-msvc': 1.62.0 + '@oxlint/binding-win32-x64-msvc': 1.62.0 + oxlint-tsgolint: 0.22.1 parent-module@1.0.1: dependencies: @@ -2476,9 +2491,9 @@ snapshots: dependencies: semver-compare: 1.0.0 - postcss-safe-parser@7.0.1(postcss@8.5.8): + postcss-safe-parser@7.0.1(postcss@8.5.13): dependencies: - postcss: 8.5.8 + postcss: 8.5.13 postcss-selector-parser@7.1.1: dependencies: @@ -2493,9 +2508,9 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 - postcss@8.5.8: + postcss@8.5.13: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -2503,9 +2518,9 @@ snapshots: punycode.js@2.3.1: {} - qified@0.6.0: + qified@0.9.1: dependencies: - hookified: 1.15.1 + hookified: 2.2.0 queue-microtask@1.2.3: {} @@ -2579,7 +2594,7 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@8.2.0: + string-width@8.2.1: dependencies: get-east-asian-width: 1.5.0 strip-ansi: 7.2.0 @@ -2597,20 +2612,20 @@ snapshots: dependencies: ansi-regex: 6.2.2 - stylelint-config-recommended@18.0.0(stylelint@17.4.0(typescript@5.6.2)): + stylelint-config-recommended@18.0.0(stylelint@17.9.1(typescript@5.6.2)): dependencies: - stylelint: 17.4.0(typescript@5.6.2) + stylelint: 17.9.1(typescript@5.6.2) - stylelint-config-standard@40.0.0(stylelint@17.4.0(typescript@5.6.2)): + stylelint-config-standard@40.0.0(stylelint@17.9.1(typescript@5.6.2)): dependencies: - stylelint: 17.4.0(typescript@5.6.2) - stylelint-config-recommended: 18.0.0(stylelint@17.4.0(typescript@5.6.2)) + stylelint: 17.9.1(typescript@5.6.2) + stylelint-config-recommended: 18.0.0(stylelint@17.9.1(typescript@5.6.2)) - stylelint@17.4.0(typescript@5.6.2): + stylelint@17.9.1(typescript@5.6.2): dependencies: - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) @@ -2624,23 +2639,22 @@ snapshots: fastest-levenshtein: 1.0.16 file-entry-cache: 11.1.2 global-modules: 2.0.0 - globby: 16.1.1 + globby: 16.2.0 globjoin: 0.1.4 html-tags: 5.1.0 ignore: 7.0.5 import-meta-resolve: 4.2.0 - imurmurhash: 0.1.4 is-plain-object: 5.0.0 mathml-tag-names: 4.0.0 meow: 14.1.0 micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.8 - postcss-safe-parser: 7.0.1(postcss@8.5.8) + postcss: 8.5.13 + postcss-safe-parser: 7.0.1(postcss@8.5.13) postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - string-width: 8.2.0 + string-width: 8.2.1 supports-hyperlinks: 4.4.0 svg-tags: 1.0.0 table: 6.9.0 @@ -2666,7 +2680,7 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.18.0 + ajv: 8.20.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 27d266014..0745ec4cb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,8 @@ # 9 days, in minutes minimumReleaseAge: 12960 +minimumReleaseAgeExclude: + - wasm-pack + - echarts packages: - "dev-docs" - "packages/backend/pkg" @@ -7,3 +10,4 @@ packages: - "packages/document-methods" - "packages/ui-components" - "packages/gaios" + diff --git a/rfc/0001.md b/rfc/0001.md index 03dae2e12..8645da80d 100644 --- a/rfc/0001.md +++ b/rfc/0001.md @@ -370,7 +370,6 @@ DOTS [@libkind-myers-2025]. ### Instances -\taxon{alternative} My original vision for "set-theoretic models" in CatColab is that they would exist one level lower in the system: as [instances](double-instances-2026) over @@ -411,16 +410,16 @@ this idea. Tim Hosgood has written up essentially the same approach to polynomial ODE systems in the [roadmap](https://github.com/ToposInstitute/CatColab-Roadmap/issues/58). -My paper with Michael Lambert on double ologs [@lambert-patterson-2024] shares ideas -with this RFC and is a useful source of intuition and examples. But there are -notable differences: here we envision semantics in $\SSet$, instead of $\Rel$ as in -the paper, and here we are interested in models given by finite presentation, -instead of explicitly by tabular data as in a database. We also currently have -no plans to implement the compact closed structure from the paper, which remains -poorly understood for weak double categories, let alone for virtual double -categories, and is the subject of ongoing research. The next section describes a -situation where the structure of a compact closed double category would be -useful, however. +My paper with Michael Lambert on double ologs [@lambert-patterson-act-2024] +shares ideas with this RFC and is a useful source of intuition and examples. But +there are notable differences: here we envision semantics in $\SSet$, instead of +$\Rel$ as in the paper, and here we are interested in models given by finite +presentation, instead of explicitly by tabular data as in a database. We also +currently have no plans to implement the compact closed structure from the +paper, which remains poorly understood for weak double categories, let alone for +virtual double categories, and is the subject of ongoing research. The next +section describes a situation where the structure of a compact closed double +category would be useful, however. ## Future possibilities diff --git a/rfc/0002.md b/rfc/0002.md index 260fc272d..dd614ccf2 100644 --- a/rfc/0002.md +++ b/rfc/0002.md @@ -450,7 +450,7 @@ The following rules are specific to DoubleTT. #### Hom types - *Hom type former*: For each object in the double theory, there is a - family of Hom types, parameterized by pairs of object terms: + family of Hom types, parameterized by pairs of objects: ```tikz \[ @@ -507,7 +507,7 @@ The following rules are specific to DoubleTT. #### Morphism types - *Morphism type former*: For each proarrow in the double theory, there - is a family of morphism types, parameterized by pairs of object terms: + is a family of morphism types, parameterized by pairs of objects: ```tikz \[ diff --git a/rfc/0004.md b/rfc/0004.md index 7cd0f1560..03ee0e896 100644 --- a/rfc/0004.md +++ b/rfc/0004.md @@ -4,10 +4,16 @@ title: | author: Evan Patterson date: 2026-04-10 bibliography: _references.bib +tikz-preamble: | + \newcommand{\eqtype}{\mathsf{eq}} + \newcommand{\letin}[1]{\mathsf{let}\;{#1}\;\mathsf{in}\;} --- {{< include _macros.qmd >}} +\newcommand{\eqtype}{\mathsf{eq}} +\newcommand{\letin}[1]{\mathsf{let}\;{#1}\;\mathsf{in}\;} + ::: {.callout-note} ### RFC status @@ -17,17 +23,18 @@ This RFC is tracked in ## Summary -Any pragmatic tool for "programming" inside categorical structures will include -a pointful notation to define morphisms (programs), based on variables and -function application as in a conventional programming language. Such a notation -is the *internal language* for the categorical structure. This RFC seeks a -uniform method to construct the internal languages of different categorical -structures, suitable for implementation in CatColab. The technical approach -involves a two-level type theory parameterized by a modal double theory. +Any pragmatic tool for programming inside categorical structures will allow +morphisms to be denoted using variables and function applications, as in +conventional programming languages. In the jargon, such a notation based on +variables is called the *internal language* of the categorical structure. This +RFC seeks a uniform method to construct the internal languages of different +categorical structures, suitable for implementation in CatColab. The technical +approach involves a two-level type theory parameterized by a modal double +theory. ## Motivation -Several styles of notation are commonly used to denote a morphism in a +Several styles of notation are commonly used to denote morphisms in a categorical structure. Among text-based notations, the most important distinction is between **point-free** notations, which do not involve "elements" or "variables," and **pointful** notations, which do. To illustrate, suppose @@ -38,8 +45,8 @@ or $x: X \vdash g(f(x)): Z$ (to use a type-theoretic syntax). In a bare category, the most general data that can be composed is a path of morphisms, and there is little practical difference between point-free and -pointful notations. However, as more categorical structure is introduced, the -difference can become dramatic. As another example---still tiny by practical +pointful notations. However, in richer categorical structures, the difference +can be dramatic. As another illustration---still tiny by practical standards---suppose that $f: X \times Y \to W$, $g: X \to W'$, and $h: W \times W' \to Z$ are morphisms in a cartesian category. The composite morphism with signature $X \times Y \to Z$ can be written in point-free notation as @@ -52,11 +59,11 @@ which the reader must exert some effort to compile in their head, in contrast to the pointful notation $$ -x : X,\ y : Y \vdash h(f(x, y), g(x)) : Z, +(x, y): X \times Y \vdash h(f(x, y), g(x)) : Z, $$ which is just the familiar syntax for composing functions. Besides its -familiarity, the key ergonomic advantage of pointful notation is that operations +familiarity, a key ergonomic advantage of pointful notation is that operations like copying and permuting, which must be *explicitly* stated in point-free notation, become *implicit*. @@ -74,10 +81,10 @@ using variables. That's what the [`Path`](https://next.catcolab.org/dev/rust/catlog/one/path/enum.path) data structure in `catlog` does. -Finding canonical forms becomes more difficult as categorical structure -accumulates. For example, let $X \xto{f} Y \xto{g} Z$ and $X' \xto{f'} Y' -\xto{g'} Z'$ be morphisms in a monoidal category. In point-free syntax, two -different expressions, +As categorical structure accumulates, finding canonical forms becomes more +difficult. For example, let $X \xto{f} Y \xto{g} Z$ and $X' \xto{f'} Y' \xto{g'} +Z'$ be morphisms in a monoidal category. In point-free syntax, two different +expressions, $$ (g \circ f) \otimes (g' \circ f') @@ -87,47 +94,59 @@ $$ denote the same morphism, by the interchange law. Moreover, due to the symmetry of the situation, there is no obvious reason to prefer one over the other. In -the pointful notation of a suitable type theory, this symmetry is broken, and -there will be a canonical way to denote this morphism, namely +the pointful notation of a suitable type theory, this symmetry is broken: of the +corresponding two ways to denote this morphism, $$ -(x, x'): X \otimes X' \vdash (g(f(x)), g'(f'(x'))): Z \otimes Z'. +(x, x'): X \otimes X' \vdash (g(f(x)), g'(f'(x'))): Z \otimes Z' $$ -Intuitively, this term corresponds to the first of the above point-free -expressions, whereas the second has no counterpart because forming and applying -the tensored operation $g \otimes g'$ is not allowed. This point of view---that -type theory is a toolkit to find canonical forms for morphisms---is a theme in -Shulman's lecture notes on categorical logic [@shulman-2016]. +and + +$$ +(x, x'): X \otimes X' \vdash \letin{(y, y') = (f(x), f(x'))} (g(y), g(y')): Z \otimes Z', +$$ + +the former is canonical as it contains no eliminable let bindings, whereas the +latter does. This general point of view---that type theory is a toolkit to find +canonical forms for morphisms---is a theme in Shulman's lecture notes on +categorical logic [@shulman-2016]. In short, among textual notations, pointful notation has both practical advantages, being familiar and ergonomic, as well as theoretical ones, inasmuch -as it sometimes leads to canonical forms. +as it can help find canonical forms. + +In CatColab, we plan to use pointful notation in several places. First, to +specify equations between morphisms when finitely presenting a model. From the +perspective that a model of a double theory is a *theory* in the sense of +(one-dimensional) categorical logic, such equations are *axioms* of the theory. +This feature is covered by the type theory presented in this RFC. In addition, +we intend to use pointful notation in specifying the morphism part of a morphism +between models, i.e., an *interpretation* of one theory in another, and also, +moving up a dimension, in specifying the components of a transformation between +model morphisms. The details of these features are beyond the scope of this RFC. Before moving on, it's worth acknowledging that point-free notation is not without its own merits. Conveniently for computer implementation, point-free -notation can be mechanically extracted for any categorical structure defined in -a suitable formal language, such as GATs or double theories. In contrast, while -the field of type theory has recurring principles and techniques, designing *a* -type theory remains a fiddly business. Finding the [internal -language](https://ncatlab.org/nlab/show/internal+logic) of a new categorical -structure---a type theory whose types and terms faithfully denote such -categories' objects and morphisms---is often a research problem in its own -right. So, the research problem behind this RFC is that while CatColab has a -uniform meta-logic for (parts of) logic at the level of categorical *semantics*, -we now need a uniform approach at the level of type-theoretic *syntax*. - -Not only is point-free syntax readily available for *any* categorical structure, -it is equivariant under categorical duality, whereas pointful notation is -intrinsically biased toward the domain of a morphism (its inputs) and also, to a -lesser extent, toward cartesian logics, in which variables can be freely -duplicated and discarded. The happiest categorical semantics for pointful -notation is a free cartesian multicategory or, what is nearly the same, a free -cartesian category whose basic morphisms have codomains that are basic objects. -The further one departs from this ideal, the more likely standard techniques are -to break down. Nevertheless, providing an intuitive pointful syntax seems so -indispensable to the vision of CatColab as a family of interoperable programming -languages that we cannot neglect to pursue a unified approach. +notation can be mechanically extracted for categorical structures defined in any +reasonable formal language, such as GATs or double theories. In contrast, +finding the [internal language](https://ncatlab.org/nlab/show/internal+logic) of +a new categorical structure---a type theory whose types and terms correspond +precisely to the structure's objects and morphisms---is often a research problem +in its own right. The research problem behind this RFC is that while CatColab +has a uniform meta-logic for (parts of) categorical logic at the level of +*semantics*, we now seek a uniform account of the corresponding type-theoretic +*syntax*. + +In addition to being readily available for any categorical structure, point-free +notation is equivariant under categorical duality. Pointful notation, on the +other hand, is intrinsically biased toward the domain of a morphism (its inputs) +and also, to a lesser extent, toward cartesian logics, in which variables can be +freely duplicated and discarded. The further one departs from the ideal +semantics of a cartesian multicategory, the more likely standard techniques are +to break down. Nevertheless, we aim to push a uniform approach as far as we are +able, as providing a familiar pointful notation seems indispensable to the +vision of CatColab as a family of interoperable programming languages. ## Technical approach @@ -145,7 +164,7 @@ of the type theory itself. [^no-signatures]: Type theories with function types and enough inductive types to generate standard data types such as the booleans and natural numbers commonly omit a - parameterizing signature entirely, under idea that all other types and + parameterizing signature entirely, under the idea that all other types and operations should be built out of the provided ones. In this RFC, however, we are exclusively concerned with type theories that do *not* have function types, i.e., with the internal languages of categorical structures that are @@ -163,36 +182,44 @@ a double theory. The second and third levels belong to the type theory itself. Depending on the double theory, models will be categorical structures like categories or multicategories; in fact, since they are finitely presented, such models are given by combinatorial structures like graphs or multigraphs, along -with generating equations. So it is free models, internal to this type theory, -that play the role of signatures external to traditional type theories. +with generating equations. So it is free models, internalized in this type +theory, that play the role of signatures external to traditional type theories. Models of a double theory are described by a dependent type theory combining standard and specialized rules. The judgmental structure is exactly that of standard dependent type theory. The type theory has record types (equivalent to -but more ergonomic than dependent sums) and singleton types (to enable model -composition); it does *not* have dependent products or universes. To this base -is added specialized rules for the object and morphisms types of the double -theory. All this follows the original DoubleTT, with an important exception to -be described shortly. +dependent sums but more ergonomic) and singleton types (to enable model +composition); it does *not* have dependent products, equality types, or +universes. To this base is added specialized rules for the object and morphisms +types of the double theory. All of this follows the original DoubleTT, with an +important exception described below. + +New here are two additional kinds of judgments: -What is here new are two additional kinds of judgments: +1. **object terms** over an object type in the theory, and +2. **morphism terms**, or just **terms**, over a morphism type in the theory. -1. **contexts** over an object type, and -2. **terms in context**, or just **terms**, over a morphism type. +Morphism terms are the usual terms of an internal language. Object terms are +less conventional, playing the role that contexts usually do. In both simple and dependent type theory, contexts are lists of typed variables, -with the list structure belonging to the *meta*-theory. We are forced depart -from this tradition since our internal languages encompass both unary type -theories [@shulman-2016, Chapter 1] and multi-ary ("simple") type theories -[@shulman-2016, Chapter 2]; depending on the object type, contexts may or may -not be lists. Instead, we rely on the list modality in a modal double theory to -supply the list structure needed for multi-ary internal languages. So, again, -something usually external to the type theory is internalized. The price paid is -that operations like flattening nested lists, usually swept under the -meta-theoretic rug, must now be explicitly handled within in the type theory. - -Another key design goal is that the type theory for morphisms be **cut-free**. -That is, composition of morphisms should be not a primitive operation but an +with the list structure belonging to the *meta*-theory. We depart from this +tradition since our internal languages encompass both unary type theories +[@shulman-2016, Chapter 1] and multiary ("simple") type theories [@shulman-2016, +Chapter 2]. Depending on which object type it is over, an object term may or may +not be a list. To avoid confusion, we therefore avoid calling object terms +"contexts."[^contexts-vs-object-terms] Here the list structure is supplied by a +modality available in modal double theories. So, again, a structure usually +external to the type theory is internalized. The price paid is that operations +like flattening nested lists, usually swept under the meta-theoretic rug, must +now be explicitly handled within in the type theory. + +[^contexts-vs-object-terms]: + Thanks to Mitchell Riley for pointing out that the "contexts" in the inner + type theory are more like terms. + +Another design goal is that the type theory for morphisms be **cut-free**. That +is, composition of morphisms should be not a primitive operation but an **admissible rule**: a meta-theoretic operation derivable from the other rules of the system. Eliminating primitive rules in favor of admissible ones is how type theory can produce canonical forms. Similarly, the type theory will allow @@ -202,52 +229,216 @@ morphisms like $f \cdot g$ and $[f, g]$ are not part of the type theory. ## Mathematical specification +Turning to the mathematical spec, we present its three levels in the logical +order: first, double theories; then models of a double theory, the subject of +the outer type theory; and finally morphisms in a model, the subject of the +inner type theory. That is the only sensible way to write the document. Still, +the reader may find it easier to proceed in the opposite order, starting with +the [examples](#examples) and working backward through the three levels of the +spec. + ### Double theories -For the purposes of this RFC, we reformulate a double theory as a structure -interpolating between an arbitrary virtual double category (VDC) and a -*representable* VDC (equivalent to a genuine double category). +This RFC builds on the framework for double-categorical logic studied by the +author and collaborators for some time now. However, needs from the type theory +have led to us subtly reformulate the main definitions, so we present them again +in a self-contained if terse style. + +#### Almost representability + +\newcommand{\VDC}{\mathbf{VDC}} +\newcommand{\arVDC}{\mathbf{arVDC}} +\newcommand{\arVDCstrict}{\mathbf{arVDC}_{\mathrm{str}}} + +We refine the notion of double theory to interpolate between an arbitrary +virtual double category (VDC) and a *representable* VDC (equivalent to a genuine +double category). **Definition**. A virtual double category is **almost representable** if for -every multicell, every subpath of its domain has a composite. +every multicell, every subpath of proarrows in its domain has a composite. Equivalently, by the universal property of composites, a VDC is almost representable if every multicell factors through a composition cell and, moreover, every composition cell can be factored arbitrarily as a tight -composite of binary and nullary composition cells. In such a VDC, the analysis -of an arbitrary multicell reduces to three simpler cases: (1) unary cells, as in -an ordinary double category; (2) binary composition cells; and (3) nullary -composition cells, i.e., extension cells for units. This case analysis will -appear directly the rules of our type theory. - -**Definition**. A **double theory** is an almost representable [virtual -equipment](https://ncatlab.org/nlab/show/virtual+equipment), i.e., an almost -representable VDC with units and restrictions. - -In practice, double theories are finitely presented, but formalizing such -presentations is a problem for future work. - -**Definition**. A **model** of a double theory $\dbl{T}$ is a functor of VDCs -$\dbl{T} \to \SSet$ that preserves restrictions (but not composites!); -equivalently, a model of $\dbl{T}$ is a functor of virtual equipments $\dbl{T} -\to \CCat$, i.e., a *normal* functor of VDCs that preserves restrictions (but -not composites of arity greater than 1). - -We will use the term "double theory" somewhat loosely to refer to any object -with *at least* the structure postulated above. A **simple double theory** is a -double theory with *exactly* that structure. The only other double doctrine that -we will treat is modal double theories. The list modalities will be crucial for -extracting familiar-looking internal languages for "multi-ary" categorical -structures like multicategories, monoidal categories, PROs, and generalizations -thereof. +composite of binary and nullary composition cells. Thus, while such a VDC is not +necessarily representable---there may be composable proarrows that do not have a +composite---this failure is anodyne in that it cannot affect any multicell +actually appearing in the VDC. + +The analysis of an arbitrary multicell in an almost representable VDC reduces to +three simpler cases: (1) unary cells, as in a genuine double category; (2) +binary composition cells; and (3) nullary composition cells, i.e., extension +cells for units. This case decomposition will appear directly in the rules of +our type theory. + +To avoid needless complications with coherence, we will also ask that our double +theories be as strict as possible.[^strictness] + +[^strictness]: + This is consistent with @lambert-patterson-2024, who, while using genuine + rather than virtual double categories for double theories, still assume them + to be strict. Thanks to tslil clingman for noticing the complications that + arise in the type theory when we do not assume strictness. + +**Definition**. An almost representable VDC is **strict** when it is equipped +with a strictly associative and unital choice of composites, for all subpaths of +proarrows in the domain of a multicell. + +Whenever we write loose composites or identities in a double theory, we will +mean the chosen ones. + +**Definition**. + +- The **2-category of almost representable VDCs**, denoted $\arVDC$, has as + objects, almost representable VDCs; as morphisms, functors (of VDCs) that + preserve composites whenever they exist; and as 2-morphisms, natural + transformations. +- The **2-category of strict almost representable VDCs**, denoted + $\arVDCstrict$, has as objects, strict almost representable VDCs; as + morphisms, functors that strictly preserve the chosen composites; and as + 2-morphisms, natural transformations. + +Notice that $\arVDC$ is a locally full sub-2-category of $\VDC$, the 2-category +of VDCs, whereas $\arVDCstrict$ merely has a locally fully faithful 2-functor to +$\VDC$. + +#### Simple theories + +The most basic notion of double theory is: + +**Definition** [cf. @lambert-patterson-2024, Section 3]. + +- A **simple double theory** is a strict almost representable [virtual + equipment](https://ncatlab.org/nlab/show/virtual+equipment), i.e., a strict + almost representable VDC having all units and restrictions. +- A **model** of a simple double theory $\dbl{T}$ is a + functor[^preserving-restrictions] of VDCs $M: \dbl{T} \to \SSet$; + equivalently, a model of $\dbl{T}$ is a functor of virtual equipments $M: + \dbl{T} \to \CCat$, i.e., a *normal* functor of VDCs. + +[^preserving-restrictions]: + A functor (of VDCs) between virtual equipments automatically preserves + restrictions, a not-so-obvious fact [@cruttwell-shulman-2010, Theorem 7.24]. + On the other hand, such a functor need not preserve any composites, including + units. Both of these properties are intended of a model of a double theory. + +In practice, double theories are finitely presented. A type theory for models, +including this one, should really be parameterized by a *finite presentation* of +a theory rather than by the theory itself. However, we leave the problem of +formalizing such presentations to future work. + +#### Flatness + +Recall that a (virtual) double category is **flat**, also called **locally +thin**, if a cell is determined by its boundary, i.e., if for each possible cell +boundary, there is at most one cell filling it [@grandis-2019, Section 3.2.1]. + +**Assumption**. The double theory parameterizing this type theory is assumed to +be flat. + +This assumption might seem both strong and strange, but it's not hard to +motivate. Recall that the (tight) arrows of a double theory are viewed as +operations acting on objects and the cells as operations acting on morphisms. In +a pointful notation, then, arrows should act on types, which correspond to +objects, and also on variables and terms, which represent elements of types. For +example, a product operation in the double theory should act on types to form +product types and on terms to form pairs. But in pointful notation, types and +elements are all there is! As there is nothing else in the syntax for cells to +act on, any cell being applied must be unambiguously determined by its boundary. +That's exactly what flatness says. + +When a double theory is given by finite presentation, proving flatness can be +nontrivial, amounting to a coherence theorem. #### Modal theories -**TODO** +Besides simple double theories, the other double-categorical doctrine that we +will need is modal double theories. In particular, the family of list modalities +is essential to extract familiar-looking internal languages for "multiary" +categorical structures like multicategories and monoidal categories. For the +vast majority of the examples below, the following special case of the +definition suffices. The reader may wish to take this as *the* definition on a +first reading. + +**Definition** (special case). + +- A **modal double theory** is a monad $(\dbl{T},T)$ in $\arVDCstrict$, the + 2-category of strict almost representable VDCs, whose underlying object + $\dbl{T}$ is a virtual equipment. +- Let $S$ be a monad on $\SSet$ in the 2-category of VDCs. A **model** of a + modal double theory $(\dbl{T},T)$ in $(\SSet,S)$ is a strict morphism of + monads $M: (\dbl{T},T) \to (\SSet,S)$ in the 2-category of VDCs. + +Occasionally we need a double theory equipped with multiple modalities, possibly +related to each other. Generalizing the idea of a double theory equipped with a +single monad, a *mode theory* is used to axiomatize the available modalities, +which could be monads, comonads, or other things besides. + +**Definition** (general). + +- A **mode theory** is a strict monoidal category $\cat{M}$. +- A **modal double theory** with mode theory $\cat{M}$ is a 2-functor $\cal{T}: + \mathbf{B}(\cat{M}) \to \arVDCstrict$ from the delooping of $\cat{M}$ to the + 2-category of strict almost representable VDCs, such that the image $\dbl{T} + \coloneqq \cal{T}(*)$ of the unique object is a virtual equipment. +- Let $\cal{S}: \mathbf{B}(\cat{M}) \to \VDC$ be a 2-functor such that + $\cal{S}(*) = \SSet$. A **model** of a modal double theory $\cal{T}$ in + $\cal{S}$ is a 2-natural transformation: + +```tikz +% https://q.uiver.app/#q=WzAsMyxbMCwxLCJcXG1hdGhiZntCfShcXG1hdGhzZntNfSkiXSxbMSwwLCJcXG1hdGhiZnthclZEQ31fe1xcbWF0aHJte3N0cn19Il0sWzIsMSwiXFxtYXRoYmZ7VkRDfSJdLFswLDEsIlxcY2Fse1R9Il0sWzAsMiwiXFxjYWx7U30iLDJdLFsxLDJdLFsxLDQsIiIsMix7InNob3J0ZW4iOnsic291cmNlIjozMCwidGFyZ2V0IjozMH19XV0= +\[\begin{tikzcd}[column sep=small] + & {\mathbf{arVDC}_{\mathrm{str}}} & \\ + {\mathbf{B}(\mathsf{M})} && {\mathbf{VDC}} + \arrow[from=1-2, to=2-3] + \arrow["{\cal{T}}", from=2-1, to=1-2] + \arrow[""{name=0, anchor=center, inner sep=0}, "{\cal{S}}"', from=2-1, to=2-3] + \arrow[between={0.3}{0.7}, Rightarrow, from=1-2, to=0] +\end{tikzcd}\] +``` + +This definition recovers the previous one by taking the mode theory to be the +walking monoid. Thinking of the mode theory as a 2-category with one object, +this choice corresponds to the [walking +monad](https://ncatlab.org/nlab/show/walking+monad). #### List modalities -**TODO** +The *list monad*, or *free monoid monad*, is among the most famous examples of a +monad. Upgrading the list monad on $\Set$ to a double monad on $\SSet$ creates +new degrees of freedom in how a list of arrows relates to its lists of sources +and targets. + +One such degree of freedom is captured by the following data. Let $\cat{F}$ be +any wide subcategory of the skeleton of $\FinSet$ that is a *faithful cartesian +club* [@shulman-2016, Definition 2.6.3]. Examples of interest are the +subcategories consisting of identity functions, bijections, injections, +surjections, or all functions. + +There is a double monad $\List_{\cat{F}}: \SSet \to \SSet$ that is the ordinary +list monad in degree zero, + +$$ (\List_{\cat{F}})_0 \coloneqq \List: \Set \to \Set, $$ + +and that sends a family of elements $P: A \proto B$ to the family +$\List_{\cat{F}} P: \List A \proto \List B$ whose elements + +$$ (\sigma, \vec{p}): (a_1, \dots, a_n) \proto (b_1, \dots, b_m) $$ + +consist of a function $\sigma: [m] \to [n]$ together with a list $\vec{p} = +(p_1, \dots, p_m)$ of form + +$$ p_i: a_{\sigma(i)} \proto b_i, \qquad i = 1,\dots,m. $$ + +This double monad is more precisely a monad in the 2-category $\VDC$, or, +equivalently since $\SSet$ is representable, in the 2-category of double +categories, lax double functors, and (tight) natural transformations. For +details and generalizations, see the [math +docs](https://next.catcolab.org/math/mdt-0007.xml). + +By convention, rules below that reference the double monad $\List$ without +further adornment are taken to mean $\List_{\cat{F}}$ for any choice of +$\cat{F}$. ### Outer type theory: models @@ -255,11 +446,11 @@ The outer type theory, a dependent type theory, has the standard judgments for contexts, types in context, and terms in context, as well as for equalities between types and between terms. We are agnostic as to whether substitutions are implicit or explicit. Moreover, the outer type theory has dependent record types -and singleton types, though do not actually need either for present purposes. -For details, see [RFC-0002](0002.md) and references therein. +and singleton types, though neither feature is actually needed for this RFC. For +details, see [RFC-0002](0002.md) and references therein. We state the special rules for DoubleTT in full, as there are changes from the -original spec. Let $\dbl{T}$ be a double theory. +original spec. In what follows, let $\dbl{T}$ be a double theory. #### Object types @@ -320,6 +511,47 @@ original spec. Let $\dbl{T}$ be a double theory. \] ``` +::: {.callout-warning} +#### Warning: Morphisms vs morphism generators + +We emphasize that, in contrast to the original version of DoubleTT, a term of +type $P(X,Y)$ in the outer type theory represents a morphism *generator* from +$X$ to $Y$. That's why morphism operations (unary cells in $\dbl{T}$) cannot be +applied to terms of type $P(X,Y)$. An *arbitrary* morphism from $X$ to $Y$ is +represented by a term $u: X \vdash_P t: Y$ in the inner type theory. + +::: + +#### Morphism equality types + +- *Morphism equality type former*: For each proarrow $P$ in the double theory, + there is a type of equalities between morphism terms over $P$: + + ```tikz + \[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (P: \cal{X} \proto \cal{Y})} + \hypo{\Gamma \mid u: X \vdash_P t: Y} + \hypo{\Gamma \mid u: X \vdash_P s: Y} + \infer3{\Gamma \vdash \eqtype(u: X, Y, t, s)\ \type} + \end{prooftree} + \] + ``` + + Intuitively, a term of this type witnesses an equality between the morphisms + denoted by $t$ and $s$. + +::: {.callout-warning} +#### Warning: Outer vs inner equality types + +The type above is not an equality type in the usual sense of dependent type +theory [@angiuli-gratzer-2025]. Indeed, the outer type theory does *not* have +equality types, i.e., there is no type of equalities between a pair of *outer* +terms of the same type. Rather, there is an type of equalities between a pair of +*inner* terms (morphism terms) with the same domain and codomain. + +::: + #### List modality ::: {.callout} @@ -395,67 +627,70 @@ for $\Gamma \vdash [X_1, [X_2, [X_3, [\,]]]]: \Ob_{\List\cal{X}}$. ::: {.callout-tip} #### Remark: List operations compute -We follow tradition in defining the list monad, including the monad unit -(singleton list) and monad multiplication (flattening), by induction. For -practical purposes, this presentation is not very important; we'll likely -implement lists as arrays rather than linked lists. What *is* important is that -the list operations compute: by iteratively applying the computation rules, any -applications of $\mu_{\cal{X}}$ and $\eta_{\cal{X}}$ in terms can be eliminated. +In the spirit of type theory, the rules above define the list monad, including +the monad's unit (singleton list) and multiplication (flattening), by induction, +but this presentation is not very important. In fact, the rules for terms in the +inner type theory will describe lists in an unbiased style. What *is* important +is that the list operations compute: by iteratively applying the computation +rules, any applications of $\mu_{\cal{X}}$ and $\eta_{\cal{X}}$ in terms can be +eliminated. ::: ### Inner type theory: morphisms +We now assume that $\dbl{T}$ is a *flat* double theory. + #### Judgments The inner type theory introduces two new judgments, both depending on contexts from the outer type theory. -1. *Contexts*: Presupposing that $\cal{X}$ is an object type of $\dbl{T}$ and - that $\Gamma \vdash X: \Ob_{\cal{X}}$ is an object, the judgment +1. *Object terms*: Presupposing that $\cal{X}$ is an object type of $\dbl{T}$ + and that $\Gamma \vdash X: \Ob_{\cal{X}}$ is an object, the judgment $$ \Gamma \mid u: X $$ - asserts that "$u$ is an (inner) context of type $X$, in (outer) context - $\Gamma$". + asserts that "$u$ is an (object) term of type $X$, in context $\Gamma$ and + over $\cal{X}$". -2. *Terms*: Presupposing that $P: \cal{X} \proto \cal{Y}$ is a morphism type of - $\dbl{T}$, that $\Gamma \vdash X: \Ob_{\cal{X}}$ and $\Gamma \vdash Y: - \Ob_{\cal{Y}}$ are objects, and that $\Gamma \mid u: X$ is a context of type - $X$, the judgment +2. *Morphism terms*: Presupposing that $P: \cal{X} \proto \cal{Y}$ is a morphism + type of $\dbl{T}$, that $\Gamma \vdash X: \Ob_{\cal{X}}$ and $\Gamma \vdash + Y: \Ob_{\cal{Y}}$ are objects, and that $\Gamma \mid u: X$ is an object term + of type $X$, the judgment $$ \Gamma \mid u: X \vdash_P t: Y $$ - asserts that "$t$ is an (inner) term of type $P$, in context $\Gamma \mid u : - X$". + asserts that "$t$ is a (morphism) term of type $Y$ with domain $u: X$, in + context $\Gamma$ and over $P$". When $\cal{X} = \cal{Y}$ and $P = \Hom_{\cal{X}}$, the subscript $P$ on the turnstile can be dropped. -In addition, there are judgments of equality between inner contexts and between -inner terms. +In addition, there are judgments of equality between object terms and between +morphism terms. -#### Contexts +#### Object terms ::: {.callout-warning} -#### Contexts vs lists +#### Warning: Contexts vs object terms Contexts are typically defined inductively to be lists of typed variables (i.e., -variable-type pairs), but the inner contexts of this type theory are constructed -differently. First of all, contexts need not be lists at all; contexts are unary -by default. Only over an object type of form $\List\cal{X}$ can we build -contexts like +variable-type pairs), but despite playing an analogous role, the object terms of +this type theory are structured differently. First of all, object terms need not +be lists at all. Only over an object type of form $\List\cal{X}$ can we build +object terms like $$ \Gamma \mid [x_1, x_2, x_3] : [X_1, X_2, X_3], $$ -where the $x_i$ are variables and the $X_i$ are objects of type $\cal{X}$ in -context $\Gamma$. Especially in examples, we will be lulled into writing this +where the $x_i$ are variables and the $X_i$ are objects over $\cal{X}$ in +context $\Gamma$. Especially in examples, we might be lulled into writing this more conventionally as $$ @@ -467,7 +702,9 @@ is only syntactic sugar for the former. ::: -- *Context formation*: +##### Core rules + +- *Formation rule*: ```tikz \[ @@ -479,7 +716,7 @@ is only syntactic sugar for the former. \] ``` - where $x$ is a variable assumed not to appear in the outer context $\Gamma$. + where $x$ is a variable assumed not to appear in the context $\Gamma$. - *Operation application*: @@ -533,10 +770,12 @@ is only syntactic sugar for the former. \] ``` -- *Computation rules, monad multiplication*: analogous to computation rules for - list modality above. +- *Computation rules, monad multiplication*: like the above rule, mirrors the + the computation rules for the list modality in the outer type theory. + +#### Morphism terms -#### Terms +##### Core rules - *Identity rule*: @@ -550,11 +789,11 @@ is only syntactic sugar for the former. \] ``` - where $x$ is a variable assumed not to appear in the outer context $\Gamma$. - This rule can be interpreted as applying the nullary composition cell out of + where $x$ is a variable assumed not to appear in the context $\Gamma$. This + rule can be interpreted as applying the nullary composition cell out of $\cal{X}$. -- *Composition rule*: +- *Post-composition rule*: ```tikz \[ @@ -562,14 +801,16 @@ is only syntactic sugar for the former. \hypo{\dbl{T} \vDash (\cal{X} \xproto{P} \cal{Y} \xproto{Q} \cal{Z})} \hypo{\Gamma \mid u: X \vdash_P t: Y} \hypo{\Gamma \vdash f: Q(Y, Z)} - \infer3{\Gamma \mid u: X \vdash_{P \odot Q} f\,t: Z} + \infer3{\Gamma \mid u: X \vdash_{P \odot Q} f(t): Z} \end{prooftree} \] ``` where the first premise tacitly includes the assumption that the proarrows $P$ and $Q$ have a composite. This rule can then be interpreted as applying the - binary composition cell out of $P$ and $Q$. + binary composition cell out of $P$ and $Q$. In our application syntax here and + elsewhere, parentheses are optional; thus the expressions $f(t)$ and $f\,t$ + are identical terms. - *Operation application*: for each unary cell $\alpha$ in $\dbl{T}$ as on the left, there is a rule as on the right: @@ -590,29 +831,31 @@ is only syntactic sugar for the former. \begin{prooftree} \hypo{\dbl{T} \vDash \alpha} \hypo{\Gamma \mid u: X \vdash_P t: Y} - \infer2{\Gamma \mid F(u): F(X) \vdash_Q \alpha(t): G(Y)} + \infer2{\Gamma \mid F(u): F(X) \vdash_Q G(t): G(Y)} \end{prooftree} \] ``` + + Notice that the cell's name $\alpha$ does not appear on the right-hand side, + hence the need to assume that $\dbl{T}$ is flat. Two special cases are worth recording. First, for each arrow $F: \cal{X} \to - \cal{Y}$ in $\dbl{T}$, there is an identity cell $\id_F = \Hom_F$, whose - application to a term $t$ we abbreviate as $F(t)$. With this notation, there - is a derived rule: + \cal{Y}$ in $\dbl{T}$, there is an identity cell $\id_F = \Hom_F$ and hence a + derived rule: ```tikz \[ \begin{prooftree} \hypo{\dbl{T} \vDash (F: \cal{X} \to \cal{Y})} \hypo{\Gamma \mid u: X \vdash_{\Hom_{\cal{X}}} t: X'} - \infer[dashed]2{\Gamma \mid F(u): F(x) \vdash_{\Hom_{\cal{Y}}} F(t): F(X')} + \infer[dashed]2{\Gamma \mid F(u): F(X) \vdash_{\Hom_{\cal{Y}}} F(t): F(X')} \end{prooftree} \] ``` - Second, each niche in $\dbl{T}$ as on the left can be filled with restriction - cell with domain $P(F,G): \cal{X} \proto \cal{Y}$. Leaving the application of - restriction cells to terms *implicit*, there is a derived rule: + Second, each niche in $\dbl{T}$ as on the left can be filled with a + restriction cell with domain $P(F,G): \cal{X} \proto \cal{Y}$. Hence, there is + a derived rule: ```tikz \[ @@ -628,7 +871,7 @@ is only syntactic sugar for the former. \begin{prooftree} \hypo{\dbl{T} \vDash (F,P,G)} \hypo{\Gamma \mid u: X \vdash_{P(F,G)} t: Y} - \infer[dashed]2{\Gamma \mid F(u): F(X) \vdash_P t: G(Y)} + \infer[dashed]2{\Gamma \mid F(u): F(X) \vdash_P G(t): G(Y)} \end{prooftree} \] ``` @@ -651,71 +894,1286 @@ is only syntactic sugar for the former. \hypo{\dbl{T} \vDash (F,P,G)} \hypo{\Gamma \vdash X: \Ob_{\cal{X}}} \infer[no rule]1{\Gamma \vdash Y: \Ob_{\cal{Y}}} - \hypo{\Gamma \mid F(u): F(X) \vdash_{P} t: G(Y)} + \hypo{\Gamma \mid F(u): F(X) \vdash_{P} G(t): G(Y)} \infer3{\Gamma \mid u: X \vdash_{P(F,G)} t: Y} \end{prooftree} \] ``` Consequently, terms $\Gamma \mid u: X \vdash_{P(F,G)} t: Y$ and - $\Gamma \mid F(u): F(X) \vdash_P t: G(Y)$ are in bijection. + $\Gamma \mid F(u): F(X) \vdash_P G(t): G(Y)$ are in bijection. -- *Destructuring*: **TODO** +##### Further composition rules + +In type theory, composition of morphisms manifests primarily as the +post-composition rule. This rule should always be present. However, one or both +of the following extra rules related to composition may be needed depending on +the categorical structure at hand. The pre-composition rule, enabling +pre-composition with structural morphisms, is needed for non-planar multiary +languages, like those of symmetric or cartesian multicategories. The let binding +or "general composition" rule, enabling destructuring of outputs, is needed for +co-multiary languages, like those of comulticategories or monoidal categories. +Both rules are needed in languages like those of symmetric or cartesian monoidal +categories. + +- *Pre-composition rule*: + + ```tikz + \[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (P: \cal{X} \proto \cal{Y})} + \hypo{\Gamma \mid u: X' \vdash_{\Hom_{\cal{X}}} t: X} + \hypo{\Gamma \mid v: X \vdash_P s: Y} + \hypo{\gamma: \mathsf{var}(v) \to \mathsf{var}(u)} + \infer[no rule]1{\Gamma \mid u: X' \vdash t = v[\gamma]: X} + \infer4{\Gamma \mid u: X' \vdash_P s[\gamma]: Y} + \end{prooftree} + \] + ``` + + where $\gamma$ is a function from the set of variables contained in $v$ to the + set of variables contained in $u$. Here $v[\gamma]$ denotes the *morphism* + term given by substituting in the *object* term $v$ along $\gamma$, which + makes sense because object terms are included in morphism terms in that, + whenever $\Gamma \mid v: X$, the *identity* term $\Gamma \mid v: X + \vdash_{\Hom_{\cal{X}}} v: X$ is derivable. As usual, $s[\gamma]$ denotes the + (morphism) term given by substituting in the (morphism) term $s$ along + $\gamma$.[^variable-substitution] + +- *Let binding*, or *general composition rule*: + + ```tikz + \[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (\cal{X} \xproto{P} \cal{Y} \xproto{Q} \cal{Z})} + \hypo{\Gamma \mid u: X \vdash_P t: Y} + \hypo{\Gamma \mid v: Y \vdash_Q s: Z} + \infer3{\Gamma \mid u: X \vdash_{P \odot Q} \letin{v = t} s: Z} + \end{prooftree} + \] + ``` + + where the first premise includes the assumption that $P$ and $Q$ have a + composite. This rule can be interpreted as destructuring the value $t$ and + binding it to the variables in $v$. + +[^variable-substitution]: + A more careful treatment, not on offer here, would define the substitution + operations on both kinds of terms by induction on their constructors. Still, + it should be intuitively clear what it means to simultaneously substitute one + set of variables for another. + +::: {.callout} +#### Remark: Admissibility vs derivability of $\alpha$-equivalence + +Any type theory, including this one, should exhibit some notion of +[$\alpha$-equivalence](https://ncatlab.org/nlab/show/alpha-equivalence). Here +$\alpha$-conversion appears as the *variable renaming* rule: + +```tikz +\[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (P: \cal{X} \proto \cal{Y})} + \hypo{\Gamma \mid u: X \vdash_P t: Y} + \hypo{\gamma: \mathsf{var}(u) \xto{\cong} \cod\gamma} + \infer[dashed]3{\Gamma \mid u[\gamma]: X \vdash_P t[\gamma]: Y} + \end{prooftree} +\] +``` + +Without the pre-composition rule, variable renaming is only an +[admissible](https://ncatlab.org/nlab/show/admissible+rule) rule, in that +establishing it requires an induction over all possible derivations. With the +pre-composition rule, variable renaming becomes a derivable rule, a special case +of pre-composition given by taking $X' = X$ and $t = u$ to be an identity term, +which forces $\gamma$ to be a bijection. + +::: + +::: {.callout} +##### Remark: Let binding as cut rule + +The let binding rule is none other than an explicit cut rule. So, we should +obviously omit it when proving cut admissibility for a particular internal +language. On the other hand, even when it's not logically necessary, we might +choose to include it on the pragmatic ground of being a standard feature of +programming notation. + +In contrast, the pre-composition rule is *not* a general cut rule, as it +substitutes variables with variables, not variables with arbitrary terms. + +::: ##### Lists -**TODO** +- *Formation rule*: -### Meta-theoretic results + ```tikz + \[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (P: \cal{X} \proto \cal{Y})} + \hypo{\cat{F} \vDash (\sigma: [m] \to [n])} + \hypo{\Gamma \mid u_j: X_j} + \infer[no rule, small]1{(j = 1,\dots,n)} + \hypo{\Gamma \mid u_{\sigma(i)}: X_{\sigma(i)} \vdash_P t_i: Y_i} + \infer[no rule, small]1{(i = 1,\dots,m)} + \infer4{ + \Gamma \mid [u_1, \dots, u_n]: [X_1, \dots X_n] + \vdash_{\List_{\cat{F}} P} + [t_1, \dots, t_m]: [Y_1, \dots, Y_m] + } + \end{prooftree} + \] + ``` + + where it is implicitly assumed that the domain of the morphism term in the + conclusion is well-formed, i.e., the variables appearing at any level among + the object terms $\Gamma \mid u_j : X_j$ are disjoint. + +- *Computation rule, monad unit*: + + ```tikz + \[ + \begin{prooftree} + \hypo{\dbl{T} \vDash (P: \cal{X} \proto \cal{Y})} + \hypo{\Gamma \mid u: X \vdash_P t: Y} + \infer2{\Gamma \mid [u]: [X] \vdash_{\List_{\cat{F}} P} + \eta_{\cal{Y}}(t) = [t]: [Y]} + \end{prooftree} + \] + ``` -**TODO**: Cut admissibility +- *Computation rules, monad multiplication*: extending the computation rules for + lists of objects and lists of object terms, there are computation rules that + govern the monad multiplication for lists of morphism terms. We omit the + details. ## Examples -**TODO** +### Categories + +Simplest among type theories are the *unary* ones---so simple, in fact, that +they are rarely considered by type theorists outside of pedagogy. Yet from the +categorical point of view, unary type theories are quite natural, being the +internal languages of categories that do not have products, genuine or virtual +[@shulman-2016, Chapter 1]. For us, they are the internal languages of models of +*simple* double theories.[^simple-type-theories] + +[^simple-type-theories]: + We regret that our usage of "simple" departs from the type theorist's. All of + the internal languages covered by this RFC are [simple type + theories](https://ncatlab.org/nlab/show/simple+type+theory). + +As a first example, taking the double theory to be the point, i.e., to be freely +generated by one object, recovers the internal language a bare category +[@shulman-2016, Section 1.2]. + +### Database schemas + +\newcommand{\Entity}{\mathsf{Entity}} +\newcommand{\AttrType}{\mathsf{AttrType}} +\newcommand{\Mapping}{\mathrm{Mapping}} +\newcommand{\Attr}{\mathrm{Attr}} + +\newcommand{\Person}{\mathrm{Person}} +\newcommand{\Dog}{\mathrm{Dog}} +\newcommand{\String}{\mathrm{String}} + +For a unary type theory that's a bit more interesting, consider the internal +language of a database schema, axiomatized in idealized form by the walking +proarrow, i.e., by the freely generated double theory + +$$\dbl{T} \coloneqq \big\{\Entity \xproto{\Attr} \AttrType\big\}.$$ + +Writing $\Mapping \coloneqq \Hom_{\Entity}$ for the type of mappings (foreign +keys) between entities, we can form the context representing a simple schema +involving people and dogs: + +$$ +\begin{aligned} +\Gamma \coloneqq [ +&\String: \Ob_{\AttrType}, \\ +&\Person: \Ob_{\Entity}, \\ +&\text{first-name}: \Attr(\Person, \String), \\ +&\text{last-name}: \Attr(\Person, \String), \\ +&\Dog: \Ob_{\Entity}, \\ +&\text{owner}: \Mapping(\Dog, \Person) +]. +\end{aligned} +$$ + +We can then derive the terms with domain $\Gamma \mid p: \Person$: + +1. $\Gamma \mid d: \Dog \vdash_{\Mapping} d: \Dog$ +2. $\Gamma \mid d: \Dog \vdash_{\Mapping} \text{owner}(d): \Person$ +3. $\Gamma \mid d: \Dog \vdash_{\Attr} \text{first-name}(\text{owner}(d)): \String$ + +where step 1 uses the identity rule and steps 2 and 3 use the post-composition +rule. The final term denotes the morphism that maps a dog to the first name of +its owner. + +### Promonads + +A [promonad](https://ncatlab.org/nlab/show/promonad) is a categorical structure +with two kinds of morphisms, one of which (sometimes called **tight**) can be +cast to the other (called **loose**). For example, a monad or a comonad on a +category $\cat{C}$ gives rise to a promonad whose tight morphisms are the +morphisms of $\cat{C}$ and loose morphisms are the Kleisli or co-Kleisli +morphisms. As an example of the example, there is a promonad whose objects are +measurable spaces, tight morphisms are measurable maps, and loose morphisms are +Markov kernels. + +Promonads are axiomatized by the simple double theory of +[promonads](https://next.catcolab.org/math/dct-0004.xml) [see also the blog post +@patterson-promonads-2024]. The theory is generated by an object $\cal{X}$, a +proarrow $P: \cal{X} \proto \cal{X}$, and a globular cell, the **unit** + +```tikz +% https://q.uiver.app/#q=WzAsNCxbMCwwLCJcXGNhbHtYfSJdLFsxLDAsIlxcY2Fse1h9Il0sWzAsMSwiXFxjYWx7WH0iXSxbMSwxLCJcXGNhbHtYfSJdLFswLDEsIlxcSG9tX3tcXGNhbHtYfX0iLDAseyJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJiYXJyZWQifX19XSxbMCwyLCIiLDIseyJsZXZlbCI6Miwic3R5bGUiOnsiaGVhZCI6eyJuYW1lIjoibm9uZSJ9fX1dLFsxLDMsIiIsMCx7ImxldmVsIjoyLCJzdHlsZSI6eyJoZWFkIjp7Im5hbWUiOiJub25lIn19fV0sWzIsMywiUCIsMix7InN0eWxlIjp7ImJvZHkiOnsibmFtZSI6ImJhcnJlZCJ9fX1dLFs0LDcsIlxcdGhldGEiLDEseyJzaG9ydGVuIjp7InNvdXJjZSI6MjAsInRhcmdldCI6MjB9LCJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJub25lIn0sImhlYWQiOnsibmFtZSI6Im5vbmUifX19XV0= +\[\begin{tikzcd} + {\cal{X}} & {\cal{X}} \\ + {\cal{X}} & {\cal{X}} + \arrow[""{name=0, anchor=center, inner sep=0}, "{\Hom_{\cal{X}}}"{inner sep=.8ex}, "\shortmid"{marking}, from=1-1, to=1-2] + \arrow[equals, from=1-1, to=2-1] + \arrow[equals, from=1-2, to=2-2] + \arrow[""{name=1, anchor=center, inner sep=0}, "P"'{inner sep=.8ex}, "\shortmid"{marking}, from=2-1, to=2-2] + \arrow["\theta"{description}, draw=none, from=0, to=1] +\end{tikzcd}\] +``` + +such that the composite $P \odot P = P$ exists and subject to a couple +equations, the effect of which is to make the theory flat. + +In the internal language of a promonad, we can build unary terms while tracking +whether the morphism denoted is tight or loose. Taking the context + +$$ +\Gamma \coloneqq [ + X: \Ob_{\cal{X}},\ Y: \Ob_{\cal{X}},\ Z: \Ob_{\cal{X}},\ + f: \Hom_{\cal{X}}(X,Y),\ g: P(Y,Z),\ h: \Hom_{\cal{X}}(Z,X) +], +$$ + +we can derive the terms + +1. $\Gamma \mid x: X \vdash_{\Hom_{\cal{X}}} x: X$ +2. $\Gamma \mid x: X \vdash_{\Hom_{\cal{X}}} f(x): Y$ +3. $\Gamma \mid x: X \vdash_P g(f(x)): Z$ +3. $\Gamma \mid x: X \vdash_P h(g(f(x))): X$ + +by applying the identity rule and then repeatedly post-composing. + +We can also invoke the operation application rule with the cell $\theta$ to cast +tight morphisms to loose. For example: + +$$ +\Gamma \mid x: X \vdash_{\Hom_{\cal{X}}} f(x): Y +\qquad\leadsto\qquad +\Gamma \mid x: X \vdash_P f(x): Y. +$$ + +Then post-composing with $g$, using the composite $P \odot P = P$, we derive the +term $\Gamma \mid x: X \vdash_P g(f(x)): Z$ again but by a different means. We +see now why flatness of the double theory is important: the axioms of a promonad +ensure that the two derivations yield morphisms that are equal in the intended +categorical semantics. We will return to this point after examining the internal +language of multicategories. + +### Multicategories + +Perhaps the simplest multiary language is the internal language of a (planar) +multicategory. In this highly substructural language, variables must be used +exactly once and in the same order that they are declared. The type theory here +should be compared with the *cut-free type theory for multicategories* in +[@shulman-2016, Section 2.4.1]. + +Let's begin by reviewing the modal double theory of [generalized +multicategories](https://next.catcolab.org/math/dct-000F.xml), to be +instantiated with the list modality $T = \List = \List_{\id}$. The generators of +this double theory are an object $\cal{X}$ and a proarrow $P: \List(\cal{X}) +\proto \cal{X}$. There are two key axioms, the *composition* and *normalization* +axioms: + +$$ +\List P \odot P = P(\mu_{\cal{X}}, 1_{\cal{X}}) +\qquad\text{and}\qquad +\Hom_{\cal{X}} = P(\eta_{\cal{X}}, 1_{\cal{X}}). +$$ + +By the universal property of restriction, the composition axiom amounts to +having a cell + +```tikz +% https://q.uiver.app/#q=WzAsNSxbMCwwLCJcXExpc3RcXExpc3RcXGNhbHtYfSJdLFsxLDAsIlxcTGlzdFxcY2Fse1h9Il0sWzIsMCwiXFxjYWx7WH0iXSxbMCwxLCJcXExpc3RcXGNhbHtYfSJdLFsyLDEsIlxcY2Fse1h9Il0sWzAsMSwiXFxMaXN0IFAiLDAseyJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJiYXJyZWQifX19XSxbMSwyLCJQIiwwLHsic3R5bGUiOnsiYm9keSI6eyJuYW1lIjoiYmFycmVkIn19fV0sWzIsNCwiIiwwLHsibGV2ZWwiOjIsInN0eWxlIjp7ImhlYWQiOnsibmFtZSI6Im5vbmUifX19XSxbMyw0LCJQIiwyLHsic3R5bGUiOnsiYm9keSI6eyJuYW1lIjoiYmFycmVkIn19fV0sWzAsMywiXFxtdV97XFxjYWx7WH19IiwyXSxbMSw4LCJcXHRleHR7Y29tcH0iLDEseyJsYWJlbF9wb3NpdGlvbiI6NDAsInNob3J0ZW4iOnsidGFyZ2V0IjoyMH0sInN0eWxlIjp7ImJvZHkiOnsibmFtZSI6Im5vbmUifSwiaGVhZCI6eyJuYW1lIjoibm9uZSJ9fX1dXQ== +\[\begin{tikzcd} + {\List\List\cal{X}} & {\List\cal{X}} & {\cal{X}} \\ + {\List\cal{X}} && {\cal{X}} + \arrow["{\List P}"{inner sep=.8ex}, "\shortmid"{marking}, from=1-1, to=1-2] + \arrow["{\mu_{\cal{X}}}"', from=1-1, to=2-1] + \arrow["P"{inner sep=.8ex}, "\shortmid"{marking}, from=1-2, to=1-3] + \arrow[equals, from=1-3, to=2-3] + \arrow[""{name=0, anchor=center, inner sep=0}, "P"'{inner sep=.8ex}, "\shortmid"{marking}, from=2-1, to=2-3] + \arrow["{\text{comp}}"{description, pos=0.4}, draw=none, from=1-2, to=0] +\end{tikzcd}\] +``` + +giving the composition operation in the multicategory. Recall, however, that +this binary cell cannot be directly applied in the internal language; instead, +one uses the proarrow composite $\List P \odot P$, equal to the restricted +proarrow $P(\mu_{\cal{X}},1)$, then applies the restriction cell. The +normalization axiom + +```tikz +% https://q.uiver.app/#q=WzAsNCxbMCwwLCJcXGNhbHtYfSJdLFsxLDAsIlxcY2Fse1h9Il0sWzAsMSwiXFxMaXN0XFxjYWx7WH0iXSxbMSwxLCJcXGNhbHtYfSJdLFswLDEsIlxcSG9tX3tcXGNhbHtYfX0iLDAseyJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJiYXJyZWQifX19XSxbMCwyLCJcXGV0YV97XFxjYWx7WH19IiwyXSxbMSwzLCIiLDAseyJsZXZlbCI6Miwic3R5bGUiOnsiaGVhZCI6eyJuYW1lIjoibm9uZSJ9fX1dLFsyLDMsIlAiLDIseyJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJiYXJyZWQifX19XSxbNCw3LCJcXHRleHR7cmVzfSIsMSx7InNob3J0ZW4iOnsic291cmNlIjoyMCwidGFyZ2V0IjoyMH0sInN0eWxlIjp7ImJvZHkiOnsibmFtZSI6Im5vbmUifSwiaGVhZCI6eyJuYW1lIjoibm9uZSJ9fX1dXQ== +\[\begin{tikzcd} + {\cal{X}} & {\cal{X}} \\ + {\List\cal{X}} & {\cal{X}} + \arrow[""{name=0, anchor=center, inner sep=0}, "{\Hom_{\cal{X}}}"{inner sep=.8ex}, "\shortmid"{marking}, from=1-1, to=1-2] + \arrow["{\eta_{\cal{X}}}"', from=1-1, to=2-1] + \arrow[equals, from=1-2, to=2-2] + \arrow[""{name=1, anchor=center, inner sep=0}, "P"'{inner sep=.8ex}, "\shortmid"{marking}, from=2-1, to=2-2] + \arrow["{\text{res}}"{description}, draw=none, from=0, to=1] +\end{tikzcd}\] +``` + +says that the unary multimorphisms of $P$ are in natural bijection with the +morphisms of $\cal{X}$. For the remaining axioms of the double theory, see the +page linked above. + +To illustrate the type theory, we construct the associativity axiom of a monoid +as a morphism equality type + +$$ +\Gamma \vdash \eqtype([x,y,z]: [X,X,X],\ X,\ m[m[x,y],z],\ m[x,m[y,z]])\ \type +$$ + +in the context + +$$ +\Gamma \coloneqq [X: \Ob_{\cal{X}},\ m: P([X, X], X),\ e: P([\,], X)] +$$ + +that is the signature of the theory of monoids. We build the left-hand side as a +term with domain $\Gamma \mid [x,y,z]: [X,X,X]$. The right-hand side is similar. + +To start, use the identity rule to form the term $\Gamma \mid x: X +\vdash_{\Hom_{\cal{X}}} x: X$ and similarly for a second variable $y$. Combining +these, form the list + +$$ +\Gamma \mid [x,y]: [X,X] \vdash_{\List\Hom_{\cal{X}}} [x,y]: [X,X]. +$$ + +Apply the post-composition rule at the composable proarrrows +$\List\Hom_{\cal{X}} = \Hom_{\List\cal{X}}$ and $P$ to build the term + +$$ +\Gamma \mid [x,y]: [X,X] \vdash_P m[x,y]: X. +$$ + +Next, apply the restriction rule at the theory's normalization axiom to obtain a +variable + +$$ +\Gamma \mid [z]: [X] \vdash_P z: X +$$ + +but viewed now as a unary multimorphism of $P$ rather than as a morphism of +$\cal{X}$. We can thus form the list + +$$ +\Gamma \mid [[x,y],[z]]: [[X,X],[X]] \vdash_{\List P} [m[x,y],z]: [X,X], +$$ + +apply the post-composition rule at the composable proarrows $P$ and $P$ + +$$ +\Gamma \mid [[x,y],[z]]: [[X,X],[X]] \vdash_{P(\mu_{\cal{X}}, 1)} m[m[x,y],z]: X, +$$ + +and finally apply the restriction rule again to derive the term + +$$ +\Gamma \mid [x,y,z]: [X,X,X] \vdash_P m[m[x,y],z]: X. +$$ + +As promised, this term is the left-hand side of the associativity axiom. It +would be written more conventionally as + +$$ +\Gamma \mid [x: X, y: X, z: X] \vdash m[m[x,y],z]: X +$$ + +but we caution again that in our type theory, this expression can only be +understood as an abbreviation for the previous one. + +::: {.callout-warning} +#### Warning: Non-uniqueness of derivations + +There was another, slightly longer way we could have derived the term $m[x,y]$, +parallel to how we derived the final term $m[m[x,y],z]$. Namely, after +introducing the variables $x$ and $y$, apply the restriction rule to obtain +terms $\Gamma \mid [v]: [X] \vdash_P v: X$ for variables $v = x,y$, then form +the list $\Gamma \mid [[x],[y]]: [[X],[X]] \vdash_{\List P} [x,y]: [X,X]$ and +proceed as before to build the term $\Gamma \mid [x,y]: [X,X] \vdash_P m[x,y] +\vdash X$. + +By the theory's compatibility axiom for the left action of $P$ (contributing to +the theory's flatness), both derivations denote the same multimorphism in any +model of the theory. Thus, the type theory is sound in allowing the two +derivations to produce identical terms. + +Turning this around, we see that terms in this type theory do not have unique +derivations. While that's not necessarily a problem, it can be a desideratum of +a type theory that any (derivable) term has a unique derivation. For this +particular double theory, we could achieve uniqueness of derivations by ad hocly +prohibiting use of $P$'s left and right actions. The same trick would work for +the theory of promonads. In general, though, we should not expect to have unique +derivations. + +::: + +### Symmetric multicategories + +\newcommand{\bij}{\mathrm{bij}} + +Symmetric multicategories are also models of the modal double theory of +[generalized multicategories](https://next.catcolab.org/math/dct-000F.xml), now +instantiated with the list modality $T = \List_{\bij}$. To illustrate, we derive +the commutativity axiom of a commutative monoid as an equality type + +$$ +\Gamma \vdash \eqtype([x,y]: [X,X],\ X,\ m[x,y],\ m[y,x])\ \type, +$$ + +where $\Gamma$ is the signature of the theory of monoids as before. We've +already derived the left-hand side, so let's derive the right-hand side. + +Start with the variables $\Gamma \mid x: X \vdash x: X$ and $\Gamma \mid y: X +\vdash y: X$ and apply the list formation rule using the swap permutation +$\sigma: [2] \xto{\cong} [2]$ to obtain the term + +$$ +\Gamma \mid [x,y]: [X,X] \vdash_{\List_{\bij} \Hom_{\cal{X}}} [y,x]: [X,X]. +$$ + +Then post-compose with the generator $m$ to obtain + +$$ +\Gamma \mid [x,y]: [X,X] \vdash_P m[y,x]: X. +$$ + +### Cartesian multicategories + +\newcommand{\fun}{\mathrm{fun}} + +For non-planar multicategories, the list formation and post-composition rules +used above do not suffice to build all of the expected terms; the +*pre*-composition rule is also needed. To illustrate, using the internal +language of a cartesian multicategory, we construct the distributivity axiom of +a module over a rig + +$$ +\Gamma \vdash \eqtype([r,x,y]: [R,X,X],\ X,\ + r \cdot (x + y),\ + (r \cdot x) + (r \cdot y) +)\ \type +$$ + +in the context + +$$ +\Gamma \coloneqq [ +R: \Ob_{\cal{X}},\ X: \Ob_{\cal{X}},\ +(+): P([X,X], X),\ 0: P([\,], X),\ (\cdot): P([R,X], X) +] +$$ + +that is a fragment of the signature of the theory of modules over rigs. As is +customary, the expressions $x + y$ and $r \cdot x$ are merely abbreviations for +$(+)[x,y]$ and $(\cdot)[r,x]$. We are again working with the modal double theory +of generalized multicategories, now instantiated with the list modality $T = +\List_{\fun}$. + +The left-hand side of the distributivity axiom + +$$ +\Gamma \mid [r,x,y]: [R,X,X] \vdash_P r \cdot (x + y): X +$$ + +is constructed using only the language of planar multicategory. For the +right-hand side, we proceed as before to build the intermediate term + +$$ +\Gamma \mid [r,x,s,y]: [R,X,R,X] \vdash_P (r \cdot x) + (s \cdot y): X. +$$ + +We must now apply the pre-composition rule to restrict the domain. To do so, +first form the list + +$$ +\Gamma \mid [r,x,y]: [R,X,X] \vdash_{\List_{\mathrm{fun}} \Hom_{\cal{X}}} [r,x,r,y]: [R,X,R,X] +$$ + +using the function $\sigma = [1,2,1,3]: [4] \to [3]$. Then pre-compose with this +term, with respect to the variable mapping $\gamma: \{r,s,x,y\} \to \{r,x,y\}$ +sending both $r$ and $s$ to $r$ and preserving $x$ and $y$, to derive + +$$ +\Gamma \mid [r,x,y]: [R,X,X] \vdash_P (r \cdot x) + (r \cdot y): X. +$$ + +::: {.callout-tip} +#### Compositional signatures via record types + +Alternatively, we can use record types to compositionally construct the +signature of the theory of modules. Defining the record types + +$$ +\begin{aligned} +\mathtt{SigRig} &\coloneqq \{ + X: \Ob_{\cal{X}},\ + \mathtt{add}: P([X,X], X),\ + \mathtt{zero}: P([], X),\ + \mathtt{mul}: P([X,X], X)\ + \mathtt{one}: P([], X) +\} \\ +\mathtt{SigMod} &\coloneqq \{ + R: \mathtt{SigRig},\ + X: \Ob_{\cal{X}}, + \mathtt{add}: P([X,X], X),\ + \mathtt{zero}: P([], X),\ + \mathtt{mul}: P([R.X, X], X) +\} +\end{aligned} +$$ + +for the signatures of rigs and of modules over rigs, the distributivity axiom +for scalar multiplication becomes + +$$ +\begin{aligned} +[M: \mathtt{SigMod}] \vdash \eqtype( + & [r,x,y]: [M.R.X, M.X, M.X], \\ + & M.\mathtt{mul}[r, M.\mathtt{add}[x, y]], \\ + & M.\mathtt{add}[M.\mathtt{mul}[r, x], M.\mathtt{mul}[r, y]] +). +\end{aligned} +$$ + +Similarly, the associativity axiom for scalar multiplication is + +$$ +\begin{aligned} +[M: \mathtt{SigMod}] \vdash \eqtype( + & [r,s,x]: [M.R.X, M.R.X, M.X], \\ + & M.\mathtt{mul}[M.R.\mathtt{mul}[r, s], x], \\ + & M.\mathtt{mul}[r, M.\mathtt{mul}[s, x]] +). +\end{aligned} +$$ + +::: + +### Comulticategories + +\newcommand{\concat}{\mathsf{concat}} + +Our first example of a categorical structure with "co-multiary" morphisms is a +comulticategory. This choice is rather silly as a comulticategory, having +single-input, multiple-output morphisms, is maximally ill-adapted to pointful +notation; moreover, since comulticategories are dual to multicategories, any +morphism expressed in the internal language of a mulicategory could just as well +be interpreted as a morphism in a comulticategory. Nevertheless, the example is +worth considering, first, because our type theory *does* furnish an internal +language for comulticategories, and, more importantly, because this language +illustrates features like let binding also needed in more useful languages, such +as those of PROPs or monoidal categories. + +We work with the modal double theory of *generalized comulticategories*. I've +not written down this theory explicitly, but it is just the loose dual of the +theory of [generalized +multicategories](https://next.catcolab.org/math/dct-000F.xml). The theory is +again instantiated with the list modality $T = \List = \List_{\id}$. + +Consider the associativity axiom of a comonoid + +$$ +\Gamma \vdash \eqtype(x: X,\ [X,X,X],\ + \letin{[y, c] = \delta x} \concat [\delta y, [c]],\ + \letin{[a, y] = \delta x} \concat [[a], \delta y] +)\ \type +$$ + +in the context + +$$ +\Gamma \coloneqq [X: \Ob_{\cal{X}},\ \delta: P(X,[X,X]),\ \varepsilon: P(X,[\,])] +$$ + +that is the signature of the theory of comonoids. Here we write $\concat$ for +the list monad multiplication $\mu_{\cal{X}}$ to avoid confusing Greek letters +in the signature and in the type theory itself. + +To construct, say, the left-hand side of the axiom, post-compose a variable with +$\delta$ to form $\Gamma \mid y: X \vdash_P \delta y: [X,X]$ and apply the +normalization axiom of a generalized comulticategory (where the unit +$\eta_{\cal{X}}$ now appears on the *right* boundary of the restriction cell) to +form $\Gamma \mid c: X \vdash_P [c]: [X]$. Combining these two terms, form the +list + +$$ +\Gamma \mid [y,c]: [X,X] \vdash_{\List P} [\delta y, [c]]: [[X,X],[X]]. +$$ + +Now apply the let binding rule to the term $\Gamma \mid x: X \vdash_P \delta x: +[X,X]$ and to the term above, at the composable proarrows $P$ and $\List P$, in +order to derive + +$$ +\Gamma \mid x: X \vdash_{P(1,\mu_{\cal{X}})} + \letin{[y,c] = \delta x} [\delta y, [c]]: [[X,X],[X]]. +$$ + +Finally, apply the restriction rule to obtain the desired term + +$$ +\Gamma \mid x: X \vdash_P + \letin{[y,c] = \delta x} \concat [\delta y, [c]]: [X,X,X]. +$$ + +### PROPs + +A minimal categorical structure admitting both multiary and co-multiary +morphisms is a [PRO](https://ncatlab.org/nlab/show/PRO). However, it seems that +nothing very interesting can be said in a PRO that can't already be said in a +multicategory or comulticategory. We proceed directly to the internal language +of a (multisorted) [PROP](https://ncatlab.org/nlab/show/PROP). + +PROPs are models of the modal double theory of [generalized +PROs](https://next.catcolab.org/math/dct-000E.xml), instantiated with the list +modality $T = \List_{\bij}$. The generators of this double theory are an object +$\cal{X}$, a proarrow $M: \List_{\bij} \cal{X} \proto \List_{\bij} \cal{X}$, and +a cell + +```tikz +% https://q.uiver.app/#q=WzAsNCxbMCwwLCJcXExpc3Rfe1xcYmlqfV4yIFxcY2Fse1h9Il0sWzEsMCwiXFxMaXN0X3tcXGJpan1eMiBcXGNhbHtYfSJdLFswLDEsIlxcTGlzdF97XFxiaWp9IFxcY2Fse1h9Il0sWzEsMSwiXFxMaXN0X3tcXGJpan0gXFxjYWx7WH0iXSxbMCwyLCJcXG11X3tcXGNhbHtYfX0iLDJdLFswLDEsIlxcTGlzdF97XFxiaWp9IE0iLDAseyJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJiYXJyZWQifX19XSxbMSwzLCJcXG11X1giXSxbMiwzLCJNIiwyLHsic3R5bGUiOnsiYm9keSI6eyJuYW1lIjoiYmFycmVkIn19fV0sWzUsNywiXFxvdGltZXMiLDEseyJzaG9ydGVuIjp7InNvdXJjZSI6MjAsInRhcmdldCI6MjB9LCJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJub25lIn0sImhlYWQiOnsibmFtZSI6Im5vbmUifX19XV0= +\[\begin{tikzcd}[column sep=large] + {\List_{\mathrm{bij}}^2 \cal{X}} & {\List_{\mathrm{bij}}^2 \cal{X}} \\ + {\List_{\mathrm{bij}} \cal{X}} & {\List_{\mathrm{bij}} \cal{X}} + \arrow[""{name=0, anchor=center, inner sep=0}, "{\List_{\mathrm{bij}} M}"{inner sep=.8ex}, "\shortmid"{marking}, from=1-1, to=1-2] + \arrow["{\mu_{\cal{X}}}"', from=1-1, to=2-1] + \arrow["{\mu_X}", from=1-2, to=2-2] + \arrow[""{name=1, anchor=center, inner sep=0}, "M"'{inner sep=.8ex}, "\shortmid"{marking}, from=2-1, to=2-2] + \arrow["\otimes"{description}, draw=none, from=0, to=1] +\end{tikzcd}\] +``` + +Key axioms include the *composition* and *normalization* axioms: + +$$ +M \odot M = M +\qquad\text{and}\qquad +\Hom_{\cal{X}} = M(\eta_{\cal{X}}, \eta_{\cal{X}}). +$$ + +For the remaining axioms and other details, see the linked page. + +We record the most interesting axiom of a +[bimonoid](https://ncatlab.org/nlab/show/bimonoid), namely that comultiplication +commutes with multiplication (equivalently, that multiplication commutes with +comultiplication), as the morphism equality type + +$$ +\begin{aligned} +\Gamma \vdash \eqtype( + & [x,y]: [X,X],\ [X,X], \\ + & \delta\, m [x, y], \\ + & \letin{[a,b,c,d] = \concat[\delta[x], \delta[y]]} \concat [m[a,c], m[b,d]]) +\end{aligned} +$$ + +in the context + +$$ +\Gamma \coloneqq [ +X: \Ob_{\cal{X}},\ + m: M([X,X],[X]),\ e: M([\,], [X]),\ +\delta: M([X],[X,X]),\ \varepsilon: M([X],[\,]) +] +$$ + +that is the signature of the theory of bimonoids. + +The left-hand side of this equation is built in the now familiar way: first form +the list $[x,y]$ over $\List_{\bij} \Hom_{\cal{X}}$, then post-compose with $m$ +to obtain $\Gamma \mid [x,y]: [X,X] \vdash_M m[x,y]: [X]$, and post-compose +again with $\delta$ to obtain + +$$ +\Gamma \mid [x,y]: [X]^2 \vdash_M \delta\, m [x,y]: [X]^2, +$$ + +where we introduce $[X]^k$ as an abbreviation for the list $[X,\dots,X]$ with +$X$ repeated $k$ times. + +The right-hand side is derived by applying the let binding rule to the terms + +$$ +\Gamma \mid [x,y]: [X]^2 \vdash_M \concat[\delta[x], \delta[y]]: [X]^4 +\quad\text{and}\quad +\Gamma \mid [a,b,c,d]: [X]^4 \vdash_M \concat[m[a,c], m[b,d]]: [X]^2. +$$ + +For the first of these, apply the restriction cell from the normalization axiom +to the variable $x$ to make the term $\Gamma \mid [x]: [X] \vdash_{\List_{\bij} +\Hom_{\cal{X}}} [x]: [X]$, then post-compose with $\delta$ to obtain $\Gamma +\mid [x]: [X] \vdash_M \delta [x]: [X]^2$. We do the same for the variable $y$, +then form the list + +$$ +\Gamma \mid [[x],[y]]: [[X],[X]] \vdash_{\List_{\bij} M} + [\delta[x], \delta[y]]: [[X]^2, [X]^2]. +$$ + +Finally, apply the cell $\otimes$ representing the tensor to obtain the first of +the above terms. + +As for the second term, proceed similarly to construct the term + +$$ +\Gamma \mid [[a,c], [b,d]]: [[X]^2, [X]^2] \vdash_{\List_{\bij} M} + [m[a,c],m[b,d]]: [[X], [X]], +$$ + +apply the tensor operation, and finally pre-compose with the term $\Gamma \mid +[a,b,c,d] \vdash_{\List_{\bij} \Hom_{\cal{X}}} [a,c,b,d]$ induced by the +permutation $\sigma = [1,3,2,4]: [4] \xto{\cong} [4]$. + +### Monoidal categories + +At last we come to a categorical structure whose internal language has a +non-trivial type constructor. In a monoidal category, we can form the monoidal +product $X \otimes Y$ of objects $X$ and $Y$, the linear analogue of a product +type. In fact, as objects of categorical doctrines, symmetric monoidal +categories (SMCs) and (multi-sorted) PROPs are interchangeable: any theory that +can be expressed as an SMC can just as well be expressed as a PROP, and vice +versa. The difference is between their morphisms, or interpretations of +theories: a symmetric monoidal functor, even a strict one, is more general than +a morphism of PROPs. Such morphisms are, however, beyond the scope of this RFC. +So, rather than recapitulating everything above in the language of monoidal +categories, we will focus on what changes about the syntax and how the +associators and other structure isomorphisms work. + +The internal language of monoidal categories having a non-trivial type +constructor corresponds to the modal double theory of [generalized monoidal +categories](https://next.catcolab.org/math/dct-0005.xml) having a non-structural +tight morphism. This theory is better known as the theory of *monad +pseudoalgebras* or *$T$-pseudoalgebras*, where for $T$ we can take a list double +monad. For bare monoidal categories, we take the plain list monad $T = \List = +\List_{\id}$. The generators of the theory are an object $\cal{X}$, an arrow +$\otimes: \List \cal{X} \to \cal{X}$, and *associator* and *unitor* cells + +```tikz +\[ + % https://q.uiver.app/#q=WzAsNixbMCwwLCJcXExpc3ReMiBcXGNhbHtYfSJdLFswLDEsIlxcTGlzdFxcY2Fse1h9Il0sWzEsMCwiXFxMaXN0XjIgXFxjYWx7WH0iXSxbMCwyLCJcXGNhbHtYfSJdLFsxLDEsIlxcTGlzdFxcY2Fse1h9Il0sWzEsMiwiXFxjYWx7WH0iXSxbMCwyLCIiLDAseyJsZXZlbCI6Miwic3R5bGUiOnsiYm9keSI6eyJuYW1lIjoiYmFycmVkIn0sImhlYWQiOnsibmFtZSI6Im5vbmUifX19XSxbMCwxLCJcXExpc3RcXG90aW1lcyIsMl0sWzEsMywiXFxvdGltZXMiLDJdLFsyLDQsIlxcbXVfe1xcY2Fse1h9fSJdLFs0LDUsIlxcb3RpbWVzIl0sWzMsNSwiIiwyLHsibGV2ZWwiOjIsInN0eWxlIjp7ImJvZHkiOnsibmFtZSI6ImJhcnJlZCJ9LCJoZWFkIjp7Im5hbWUiOiJub25lIn19fV0sWzYsMTEsIlxcYWxwaGEiLDEseyJzaG9ydGVuIjp7InNvdXJjZSI6MjAsInRhcmdldCI6MjB9LCJzdHlsZSI6eyJib2R5Ijp7Im5hbWUiOiJub25lIn0sImhlYWQiOnsibmFtZSI6Im5vbmUifX19XV0= + \begin{tikzcd} + {\List^2 \cal{X}} & {\List^2 \cal{X}} \\ + {\List\cal{X}} & {\List\cal{X}} \\ + {\cal{X}} & {\cal{X}} + \arrow[""{name=0, anchor=center, inner sep=0}, "\shortmid"{marking}, equals, from=1-1, to=1-2] + \arrow["{\List\otimes}"', from=1-1, to=2-1] + \arrow["{\mu_{\cal{X}}}", from=1-2, to=2-2] + \arrow["\otimes"', from=2-1, to=3-1] + \arrow["\otimes", from=2-2, to=3-2] + \arrow[""{name=1, anchor=center, inner sep=0}, "\shortmid"{marking}, equals, from=3-1, to=3-2] + \arrow["\alpha"{description}, draw=none, from=0, to=1] + \end{tikzcd} + \qquad\text{and}\qquad + % https://q.uiver.app/#q=WzAsNSxbMCwwLCJcXGNhbHtYfSJdLFswLDIsIlxcY2Fse1h9Il0sWzEsMCwiXFxjYWx7WH0iXSxbMSwyLCJcXGNhbHtYfSJdLFsxLDEsIlxcTGlzdCBcXGNhbHtYfSJdLFswLDEsIiIsMCx7ImxldmVsIjoyLCJzdHlsZSI6eyJoZWFkIjp7Im5hbWUiOiJub25lIn19fV0sWzIsNCwiXFxldGFfe1xcY2Fse1h9fSJdLFs0LDMsIlxcb3RpbWVzIl0sWzAsMiwiIiwxLHsibGV2ZWwiOjIsInN0eWxlIjp7ImJvZHkiOnsibmFtZSI6ImJhcnJlZCJ9LCJoZWFkIjp7Im5hbWUiOiJub25lIn19fV0sWzEsMywiIiwxLHsibGV2ZWwiOjIsInN0eWxlIjp7ImJvZHkiOnsibmFtZSI6ImJhcnJlZCJ9LCJoZWFkIjp7Im5hbWUiOiJub25lIn19fV0sWzgsOSwiXFxpb3RhIiwxLHsic2hvcnRlbiI6eyJzb3VyY2UiOjIwLCJ0YXJnZXQiOjIwfSwic3R5bGUiOnsiYm9keSI6eyJuYW1lIjoibm9uZSJ9LCJoZWFkIjp7Im5hbWUiOiJub25lIn19fV1d + \begin{tikzcd} + {\cal{X}} & {\cal{X}} \\ + & {\List \cal{X}} \\ + {\cal{X}} & {\cal{X}} + \arrow[""{name=0, anchor=center, inner sep=0}, "\shortmid"{marking}, equals, from=1-1, to=1-2] + \arrow[equals, from=1-1, to=3-1] + \arrow["{\eta_{\cal{X}}}", from=1-2, to=2-2] + \arrow["\otimes", from=2-2, to=3-2] + \arrow[""{name=1, anchor=center, inner sep=0}, "\shortmid"{marking}, equals, from=3-1, to=3-2] + \arrow["\iota"{description}, draw=none, from=0, to=1] + \end{tikzcd} +\] +``` + +and their loose inverses. The cells obey three coherence axioms. + +Starting with contexts, suppose $\Gamma \coloneqq [X: \Ob_{\cal{X}}, Y: +\Ob_{\cal{X}}]$ and form the object term $\Gamma \mid [x,y]: [X,Y]$ over $\List +\cal{X}$ as before. Applying the arrow $\otimes: \List\cal{X} \to \cal{X}$ +yields a new object term over $\cal{X}$ that appears literally as + +$$ +\Gamma \mid \otimes [x,y]: \otimes [X,Y]. +$$ + +Following the common practice for unbiased monoidal products, we abbreviate a +product $\otimes [X_1, \dots, X_k]$, with $k > 1$, as $X_1 \otimes \cdots +\otimes X_k$, and we write the empty product $\otimes [\,]$ as $I$. We then +introduce the tuple notation $(x_1,\dots,x_k)$ for the product element $\otimes +[x_1,\dots,x_k]$ for any $k \geq 0$. The object term above then appears in more +familiar style as + +$$ +\Gamma \mid (x, y): X \otimes Y. +$$ + +We emphasize though that the latter is only syntactic sugar for the former. + +In the context + +$$ +\Gamma \coloneqq [ + A, B, W, X, Y: \Ob_{\cal{X}},\ + f: \Hom_{\cal{X}}(A, X \otimes W),\ + g: \Hom_{\cal{X}}(W \otimes B, Y) +], +$$ + +let's construct a term denoting the morphism[^fritz-example-morphism] that is, +rather unpleasantly, written in point-free notation as + +$$ +(f \otimes 1_B) \cdot \alpha_{X,W,B} \cdot (1_X \otimes g): A \otimes B \to X \otimes Y, +$$ + +where $\alpha_{X,W,B}$ is a component of the (biased) associator natural +isomorphism. + +[^fritz-example-morphism]: + As it happens, this morphism appears as a string diagram in [@fritz-2020, + Lemma 11.12]. + +First, form the terms + +$$ +\Gamma \mid a: A \vdash_{\Hom_{\cal{X}}} fa: X \otimes W +\qquad\text{and}\qquad +\Gamma \mid b: B \vdash_{\Hom_{\cal{X}}} b: B, +$$ + +then the list + +$$ +\Gamma \mid [a,b]: [A,B] \vdash_{\List \Hom_{\cal{X}}} [fa, b]: [X \otimes W, B], +$$ + +and then apply the cell $\Hom_{\otimes}$ induced by the tensor $\otimes: +\List\cal{X} \to \cal{X}$ to obtain + +$$ +\Gamma \mid (a,b): A \otimes B \vdash_{\Hom_{\cal{X}}} (fa, b): (X \otimes W) \otimes B. +$$ + +Now, intuitively, we want to apply the let binding rule to compose this term +with + +$$ +\Gamma \mid (x,(w,b)): X \otimes (W \otimes B) \vdash_{\Hom_{\cal{X}}} + (x,g(w,b)) \vdash X \otimes Y +$$ + +constructed similarly. But the types $(X \otimes W) \otimes B$ and $X \otimes (W +\otimes B)$ don't quite match up. + +To proceed, we must use the associators and unitors to reshape the tuples. +Semantically, this amounts to applying the following chain of isomorphisms + +$$ +(X \otimes W) \otimes B \xto{\cong} + (X \otimes W) \otimes (\otimes [B]) \xto{\cong} + (X \otimes W \otimes B) \xto{\cong} + (\otimes [X]) \otimes (W \otimes B) \xto{\cong} + X \otimes (W \otimes B). +$$ + +Let's put this plan into action. Applying the unitor cell to a variable $b$ +gives $\Gamma \mid b: B \vdash (b,): \otimes [B]$. Tensor that with the pair +$(x,w)$ to get the term + +$$ +\Gamma \mid ((x,w),b): (X \otimes W) \otimes B \vdash + ((x,w),(b,)): (X \otimes W) \otimes (\otimes [B]) +$$ + +denoting the first isomorphism. Next, form the list of lists + +$$ +\Gamma \mid [[x,w],[b]]: [[X,W],[B]] \vdash_{\List \List \Hom_{\cal{X}}} + [[x,w],[b]]: [[X,W],[B]] +$$ + +and apply the associator cell to derive the term + +$$ +\Gamma \mid ((x,w),(b,)): (X \otimes W) \otimes (\otimes [B]) \vdash + (x,w,b): X \otimes W \otimes B +$$ + +denoting the second iso. Combining the two terms using the pre-composition rule +yields a new term + +$$ +\Gamma \mid ((x,w),b): (X \otimes W) \otimes B \vdash + (x,w,b): X \otimes W \otimes B +$$ + +representing the composite of the first two isomorphisms. Repeating this +procedure for the *inverse* associator and unitor cells and applying the +pre-composition rule a final time yields a term + +$$ +\Gamma \mid ((x,w),b): (X \otimes W) \otimes B \vdash_{\Hom_{\cal{X}}} + (x,(w,b)): X \otimes (W \otimes B) +$$ + +denoting the composite of all four structure isomorphisms. Intuitively, this +term represents the function that destructures the nested tuple on the left side +of the turnstile and then restructures it into the nested tuple on the right +side. + +To complete the derivation, pre-compose the original term $(x,g(w,b))$ with the +preceding term to obtain + +$$ +\Gamma \mid ((x,w),b): (X \otimes W) \otimes B \vdash_{\Hom_{\cal{X}}} + (x,g(w,b)) \vdash X \otimes Y, +$$ + +then apply the let binding rule to derive + +$$ +\Gamma \mid (a,b): A \otimes B \vdash_{\Hom_{\cal{X}}} + \letin{((x,w),b) = (fa,b)} (x,g(w,b)): X \otimes Y. +$$ + +::: {.callout-tip} +#### Remark: Comparison with programming notation + +While this derivation was rather long, it is the job of the type checker, not +the programmer, to check that a given term has a valid derivation. The term +itself is short and readable. In fact, it is nearly identical to the following +idiomatic Rust code: + +```rust +fn f(a: A) -> (X, W) { ... } +fn g(input: (W, B)) -> Y { ... } + +|(a, b): (A, B)| -> (X, Y) { + let (x, w) = f(a); + (x, g((w, b))) +} +``` +::: + +### Symmetric monoidal categories + +By now should there should be no surprises about how symmetric monoidal +categories (SMCs) arise in this framework: take the modal double theory of +[generalized monoidal categories](https://next.catcolab.org/math/dct-0005.xml), +now with the symmetric variant $T = \List_{\bij}$ of the list double monad. + +Besides the associators and unitors, SMCs have a new kind of structure +isomorphisms, the symmetries. Let's see how these appear in the internal +language. Taking $\Gamma \coloneqq [X: \Ob_{\cal{X}},\ Y: \Ob_{\cal{X}}]$ to +have two objects, we can form the list + +$$ +\Gamma \mid [x,y]: [X,Y] \vdash_{\List_{\bij} \Hom_{\cal{X}}} [y,x]: [Y,X] +$$ + +using the swap permutation $\sigma: [2] \xto{\cong} [2]$, just as we did for +symmetric multicategories. But now we can apply the cell $\Hom_{\otimes}$ +induced by the tensor $\otimes: \List_{\bij} \cal{X} \to \cal{X}$ to construct a +new term + +$$ +\Gamma \mid (x,y): X \otimes Y \vdash_{\Hom_{\cal{X}}} (y,x): Y \otimes X. +$$ + +This term denotes the symmetry isomorphism $\sigma_{X,Y}: X \otimes Y \to Y +\otimes X$. + +### Cartesian categories + +Cartesian categories, by which we mean cartesian monoidal categories, arise from +the modal double theory of generalized monoidal categories, for the cartesian +variant $T = \List_{\fun}$ of the list double monad. + +Diagonals and projections are constructed in the same manner as symmetries: one +forms the appropriate lists and then applies the cell $\Hom_{\cal{X}}$ induced +by the product operation $\otimes: \List_{\fun} \cal{X} \to \cal{X}$, +conventionally written $\times$. For example, to construct the diagonal in the +context $\Gamma \coloneqq [X: \Ob_{\cal{X}}]$, first form the list + +$$ +\Gamma \mid [x]: [X] \vdash_{\List_{\fun} \Hom_{\cal{X}}} [x,x]: [X,X] +$$ + +using the function $\sigma: [2] \xto{!} [1]$, then apply the cell +$\Hom_{\cal{X}}$, and finally pre-compose with the unitor to derive + +$$ +\Gamma \mid x: X \vdash_{\Hom_{\cal{X}}} (x,x): X \times X. +$$ + +::: {.callout-tip} +#### Remark: Comparison with standard algebraic type theory + +The internal language for cartesian categories here differs from the standard +presentation of product types [e.g., @shulman-2016, Figure 2.4] in that, while +it has the standard introduction rule for product types (pairing), it does not +have the standard elimination rules (projections). That is, given a term $t: X +\times Y$, there are no terms $\pi_1(t): X$ or $\pi_2(t): Y$ constructed by +post-composing $t$ with projection operations. Instead, projecting out a +component of an arbitrary term $t$ is a special case of destructuring, so that, +say, the first component of $t$ is given by $\letin{(x, y) = t} x: X$ or, +permitting the use of anonymous variable names, $\letin{(x, \_) = t} x: X$. + +On the other hand, the latter term is a notation supported by many programming +languages. For example, the Rust syntax is: + +```rust +let (x, _) = t; +``` +::: + +### Markov categories + +\newcommand{\Determ}{\mathsf{Determ}} +\newcommand{\Stoch}{\mathsf{Stoch}} + +\newcommand{\Predictors}{\mathrm{Predictors}} +\newcommand{\Coefficients}{\mathrm{Coefficients}} +\newcommand{\Dispersion}{\mathrm{Dispersion}} +\newcommand{\Response}{\mathrm{Response}} + +Our final example, the internal language for a version of a [Markov +category](https://ncatlab.org/nlab/show/Markov+category), is the most complex +logic treated here. The modal double theory for [Markov +categories](https://next.catcolab.org/math/dct-000I.xml), requiring a mode +theory more elaborate than that of a single monad, axiomatizes a Markov +category[^markov-category-definition] effectively as + +- a cartesian category (of **deterministic** maps), which maps into +- a [semicartesian + category](https://ncatlab.org/nlab/show/semicartesian%20monoidal%20category) + (of **stochastic** maps), via +- a [monoidal promonad](https://next.catcolab.org/math/dct-000G.xml). + +The double theory therefore combines several features used previously in +isolation. + +[^markov-category-definition]: + Be warned that this definition is not equivalent to, but is slightly more than + general than, the standard definition of a Markov category [@fritz-2020, + Definition 2.1]. It makes the deterministic maps be *structure* rather than + *property*. + +Complexity of the double theory aside, the internal language itself is no more +difficult to use than the preceding ones. To illustrate, we write down a simple +term denoting the sampling distribution of an abstract "surface plus noise" +regression model [@efron-2020]. Such a model has a deterministic component, the +*regression function*, and a stochastic component, the *noise distribution*. +Introducing the suggestive notation $\Determ \coloneqq \Hom_{\cal{X}}$ and +$\Stoch \coloneqq P$, this setup is captured by the context + +$$ +\begin{aligned} +\Gamma \coloneqq [\ + & \Predictors: \Ob_{\cal{X}}, \\ + & \Response: \Ob_{\cal{X}}, \\ + & \Coefficients: \Ob_{\cal{X}}, \\ + & \Dispersion: \Ob_{\cal{X}}, \\ + & f: \Determ(\Predictors \times \Coefficients,\, \Response) \\ + & \mathrm{noise}: \Stoch(\Response \otimes \Dispersion, \Response)\ +]. +\end{aligned} +$$ + +The sampling distribution, as a function of both the predicators and the +parameters, is then: + +$$ +\begin{aligned} +&\Gamma \mid (x, \beta, \phi): + \Predictors \otimes \Coefficients \otimes \Dispersion + \vdash_{\Stoch} \\ + & \qquad \text{noise}(f(x, \beta), \phi): \Response. +\end{aligned} +$$ + +Deriving this term is left as an exercise for the reader. ## Rationale and alternatives ### Bespoke internal languages -An obvious alternative would be to apply the usual methodology: for each -categorical structure of interest, find its internal language and produce a -bespoke implementation of that type theory. In this approach, every logic in -CatColab requiring an internal language would have its own custom text frontend. +An obvious alternative to this RFC is to apply the standard methodology: for +each categorical structure of interest, find its internal language and then +implement that specific type theory. In this approach, every logic in CatColab +requiring an internal language would have its own custom type-theoretic +frontend. + From the "semantics first" perspective of CatColab, this approach is not unreasonable. By carefully separating the textual syntax, the data structures for models and morphisms to which the syntax compiles, and the analyses that can be run on those data structures, we could hope to retain many of the benefits of -a uniform meta-logic, at least outside the frontend. Indeed, this has been our -intended approach to point-and-click *graphical* editors for graphs, Petri nets, -and so forth, whose variety seems to defy systemization. Yet I would still -rather see such an approach to internal languages as a fallback option, being at -best a pragmatic compromise to the vision of CatColab as an interoperable family -of modeling and programming languages. +a uniform meta-logic, at least outside of the frontend. Indeed, this has been +our intended approach to *graphical* editors for graphs, Petri nets, and so +forth, whose variety seems to defy systematization. Yet I would still rather see +the ad hoc approach to internal languages as a fallback option---at best a +pragmatic compromise to the vision of CatColab as an interoperable family of +programming languages. ### String diagrams Type theory is one methodology to find canonical forms for morphisms, but it's -not the only one. Among applied category theorists, the usual recipe to produce +not the only one. Among applied category theorists, the usual recipe to find canonical forms for morphisms in monoidal categories is to use *string diagrams* [@selinger-2010] or their combinatorial counterparts, *wiring diagrams* [@patterson-et-al-2020]. Incidentally, string diagrams are a point-free syntax, demonstrating that pointfulness is not a necessary property of normalizing syntax. -From our "semantics first" perspective, string diagrams are not so much an -alternative to type-theoretic textual notation as a complement to it. In -CatColab, it is the categorical structures that are primary; both graphical and -textual syntaxes should be available. Making multiple syntaxes work together -seamlessly poses architectural challenges. This document, however, is solely -about textual syntax. +String diagrams are not so much an alternative to type-theoretic textual +notation as a complement to it. Graphical and textual syntaxes each have their +pros and cons, and both should be available in CatColab. Making multiple kinds +of syntax work together seamlessly poses engineering challenges, if not also +mathematical ones. This document,however, is solely about textual syntax. ## Prior art -### Split contexts - -**TODO** +### Frameworks for substructural types theories + +To my knowledge, the most relevant prior art is Licata-Shulman-Riley's +"fibrational framework for substructural and modal logics" [@licata-2017]. Not +only do both frameworks aim to unify a range of cartesian and substructural type +theories, they share high-level features in their technical approaches. The +details diverge significantly, though. + +Both frameworks are parameterized by two-dimensional categorical structures that +determine the rules of a sequent calculus. For LSR, a *cartesian +2-multicategory* (i.e., a category-enriched cartesian multicategory) plays the +role that a *modal double theory* does here. Moreover, a *locally discrete +fibration* over a cartesian 2-multicategory plays the role that a *model* of a +modal double theory does here. The latter analogy can be made tighter by +recalling that a $\SSet$-valued lax double functor is equivalent to a *discrete +double fibration* [@lambert-2021], and presumably a similar correspondence holds +for $\SSet$-valued functors of VDCs. + +A difference in philosophy between the two accounts is that LSR start from the +standard context structure of cartesian logic (i.e., the internal language of a +cartesian multicategory) and then pass to substructural logics by tracking the +use of variables through annotations on the turnstile. In contrast, we start +from the trivial context structure of unary type theory and move up the +hierarchy of substructural logics using different versions of the list double +monad on $\SSet$. The only annotation on the turnstile is the morphism type. An +advantage of LSR is that it hews much closer to how contexts are typically +treated in type theory. An advantage of this account is that the bookkeeping for +variable use shows up only in the *derivations* of terms, rather the terms +themselves. It also avoids privileging cartesian multicategories over other +categorical structures, at least not more than pointful notation does +intrinsically. + +### Mode theories + +The concept of a *mode theory* has also appeared in work by Licata and Shulman +[-@licata-2015; @licata-2017]. Moreover, we use essentially the same definition +of a mode theory as in the first reference (a 2-category). Yet the high-level +comparison is easily confused as there is a level shift in how mode theories are +invoked here and in LSR. What LSR call the mode theory (a cartesian +2-multicategory) corresponds to a double theory, as just explained. We use a +mode theory to organize the various modalities available in modal double +theories and their models, principally the list double monads but possibly also +others not discussed here, such as the codiscrete modality. To see the +difference, in our framework the mode theory and its intended semantics in +$\SSet$ could be fixed for all logics, and that's exactly what we intend to do +in implementation. In contrast, for LSR, one chooses a different mode theory for +each logic of interest, like we choose a double theory. + +### Split contexts and two-level type theories + +Our contexts $\Gamma \mid u: X$ are a kind of [split +context](https://ncatlab.org/nlab/show/split+context) $\Gamma \mid \Delta$, +where the judgments in $\Delta$ can depend on those in $\Gamma$ but not the +other way around. In a similar spirit, our type theory can be seen as a +[two-level type theory](https://ncatlab.org/nlab/show/two-level+type+theory) +(2LTT), and we have borrowed the terms "outer type theory" and "inner type +theory" from this area. With that said, the specific work on split contexts or +2LTT that we know of has very different goals, leading to very different +mathematics. Work on 2LTT, for instance, has aimed to embed homotopy type theory +in Martin-Löf type theory. We will not attempt to survey this literature here, +but see the linked nLab pages and references therein. + +## Future possibilities -### Two-level type theories +### Meta-theoretic results -**TODO** +We have a presented a type theory and demonstrated by example that it can +reconstruct the internal languages of a range of categorical structures. +However, we have proved almost nothing about it. + +First, the type theory assumes that the parameterizing double theory is flat. +For the simpler double theories, like those of database schemas or promonads, +flatness is often obvious, but it's generally nontrivial to see that a double +theory given by finite presentation is flat. For example, flatness of the theory +of generalized monoidal categories amounts to a coherence theorem about the +associator and unitor cells. These and other flatness results should be proved. + +As for the type theory itself, at minimum, it should be proved that the type +theory is **sound** for models of any fixed double theory. That is, every +context $\Gamma \mid u: X$ should denote a well-defined object in the model +generated by $\Gamma$, and every term $\Gamma \mid u: X \vdash_P t: Y$ should +denote a well-defined morphism in that model. The type should also be +**complete** in the sense that every morphism in the model generated by $\Gamma$ +is denoted by some term. Beyond this, it should be possible to establish the +expected meta-theoretic properties of particular internal languages. For +example, the internal languages of planar, symmetric, and cartesian +multicategories (omitting the let binding rule) should make the cut rule +admissible. + +### Validation against more theories + +We are far from exhausting the categorical structures within reach of this +formalism. Here a few examples going beyond those presented here but still +fitting neatly in the framework. + +**Representable multicategories**. Arguably the categorical structure most +faithful to programming syntax is neither a multicategory nor a monoidal +category but a *representable* multicategory.[^representable-multicategories] In +most programming languages, functions have multiple inputs and a single output, +*and* there are product types. That's exactly what you get in a representable +multicategory. It is straightforward to axiomatize representable (generalized) +multicategories as a modal double theory extending that of generalized +multicategories; see the [math +docs](https://next.catcolab.org/math/dct-000O.xml) and references therein. + +[^representable-multicategories]: + That representable multicategories are equivalent to monoidal categories makes + them indistinguishable to the category theorist, but such a semantic + equivalence does not imply that their internal languages are equally + ergonomic. + +**Adjoint logics**. All of the double theories considered here have a single +generating object, $\cal{X}$. This restriction is by no means fundamental. We +can just as well consider categorical structures involving multiple categories +related by functors or adjunctions. Indeed, monads and adjunctions are both +axiomatized as 2-theories, hence also as simple double theories +[@lambert-patterson-2024, Section 3]. As a specific example, the categorical +semantics of mixed [linear/non-linear logic +](https://ncatlab.org/nlab/show/linear-non-linear+logic) (LNL) is a cartesian +closed category and a symmetric monoidal closed category, connected by a +symmetric monoidal adjunction [@benton-1994]. Apart from the monoidal +closedness, this structure would be straightfoward to axiomatize as a modal +double theory. It would then be interesting to unwind the type theory for LNL +furnished by this double theory. diff --git a/rfc/_macros.qmd b/rfc/_macros.qmd index 4a058398b..517d710c8 100644 --- a/rfc/_macros.qmd +++ b/rfc/_macros.qmd @@ -1,4 +1,6 @@ +\renewcommand{\cal}[1]{\mathcal{#1}} \renewcommand{\vec}[1]{\underline{#1}} +\newcommand{\vecvec}[1]{\underline{\underline{#1}}} \newcommand{\Ob}{\operatorname{Ob}} @@ -11,6 +13,7 @@ \newcommand{\cat}[1]{\mathsf{#1}} \newcommand{\Set}{\cat{Set}} +\newcommand{\FinSet}{\cat{FinSet}} \newcommand{\dbl}[1]{\mathbb{#1}} @@ -29,9 +32,9 @@ \newcommand{\proTo}{\mathrel{\mkern3mu\vcenter{\hbox{$\shortmid$}}\mkern-10mu{\Rightarrow}}} -\newcommand{\List}{\mathrm{List}} -\newcommand{\Disc}{\mathop{\mathrm{Disc}}} -\newcommand{\coDisc}{\mathop{\mathrm{coDisc}}} +\newcommand{\List}{\operatorname{List}} +\newcommand{\Disc}{\operatorname{Disc}} +\newcommand{\coDisc}{\operatorname{coDisc}} \newcommand{\context}{\mathsf{cx}} diff --git a/rfc/_references.bib b/rfc/_references.bib index 66bed68d9..759d81edc 100644 --- a/rfc/_references.bib +++ b/rfc/_references.bib @@ -28,6 +28,15 @@ @article{baez-et-al-2023 eprinttype = {arXiv} } +@inproceedings{benton-1994, + title = {A mixed linear and non-linear logic: Proofs, terms and models}, + author = {Benton, P. Nick}, + booktitle = {International Workshop on Computer Science Logic}, + pages = {121--135}, + year = {1994}, + doi = {10.1007/BFb0022251} +} + @article{cruttwell-shulman-2010, author = {Shulman, Michael A. and Cruttwell, G.S.H.}, title = {A unified framework for generalized multicategories}, @@ -36,8 +45,8 @@ @article{cruttwell-shulman-2010 volume = {24}, number = {21}, pages = {580–655}, - eprint = {0907.2460}, - eprinttype = {arXiv} + eprinttype = {arXiv}, + eprint = {0907.2460} } @phdthesis{dreyer-2005, @@ -48,6 +57,36 @@ @phdthesis{dreyer-2005 url = {https://csd.cmu.edu/sites/default/files/phd-thesis/CMU-CS-05-131.pdf} } +@article{efron-2020, + title = {Prediction, estimation, and attribution}, + author = {Efron, Bradley}, + journal = {International Statistical Review}, + volume = {88}, + pages = {S28--S59}, + year = {2020}, + doi = {10.1080/01621459.2020.1762613} +} + +@article{fritz-2020, + title = {A synthetic approach to Markov kernels, conditional independence and theorems on sufficient statistics}, + author = {Fritz, Tobias}, + journal = {Advances in Mathematics}, + volume = {370}, + pages = {107239}, + year = {2020}, + doi = {10.1016/j.aim.2020.107239}, + eprinttype = {arXiv}, + eprint = {1908.07021} +} + +@book{grandis-2019, + title = {Higher dimensional categories: From double to multiple categories}, + author = {Grandis, Marco}, + year = {2019}, + publisher = {World Scientific}, + doi = {10.1142/11406} +} + @book{jacobs-1998, author = {Jacobs, Bart}, title = {Categorical logic and type theory}, @@ -69,16 +108,39 @@ @article{koudenburg-2020 eprinttype = {arXiv} } +@article{lambert-2021, + title = {Discrete double fibrations}, + author = {Lambert, Michael}, + journal = {Theory and Applications of Categories}, + volume = {37}, + number = {22}, + pages = {671--708}, + year = {2021}, + eprinttype = {arXiv}, + eprint = {2101.06734} +} + @article{lambert-patterson-2024, + title = {Cartesian double theories: A double-categorical framework for categorical doctrines}, author = {Lambert, Michael and Patterson, Evan}, + journal = {Advances in Mathematics}, + volume = {444}, + pages = {109630}, + year = {2024}, + doi = {10.1016/j.aim.2024.109630}, + eprinttype = {arXiv}, + eprint = {2310.05384} +} + +@inproceedings{lambert-patterson-act-2024, title = {Representing knowledge and querying data using double-functorial semantics}, + author = {Lambert, Michael and Patterson, Evan}, + booktitle = {Proceedings of the Seventh International Conference on Applied Category Theory (ACT 2024)}, + pages = {174–-189}, year = {2024}, - journal = {Electronic Proceedings in Theoretical Computer Science}, - volume = {429}, - pages = {174–189}, doi = {10.4204/EPTCS.429.9}, - eprint = {2403.19884}, - eprinttype = {arXiv} + eprinttype = {arXiv}, + eprint = {2403.19884} } @book{leinster-2004, @@ -99,6 +161,24 @@ @article{libkind-myers-2025 eprinttype = {arXiv} } +@inproceedings{licata-2015, + title = {Adjoint logic with a 2-category of modes}, + author = {Licata, Daniel R. and Shulman, Michael}, + booktitle = {International Symposium on Logical Foundations of Computer Science}, + pages = {219--235}, + year = {2015}, + doi = {10.1007/978-3-319-27683-0_16} +} + +@inproceedings{licata-2017, + title = {A fibrational framework for substructural and modal logics}, + author = {Licata, Daniel R. and Shulman, Michael and Riley, Mitchell}, + booktitle = {2nd International Conference on Formal Structures for Computation and Deduction (FSCD 2017)}, + pages = {25:1--25:22}, + year = {2017}, + doi = {10.4230/LIPIcs.FSCD.2017.25} +} + @article{lynch-et-al-2024, author = {Lynch, Owen and Brown, Kris and Fairbanks, James and Patterson, Evan}, title = {GATlab: Modeling and Programming with Generalized Algebraic Theories}, @@ -121,6 +201,15 @@ @inproceedings{patterson-et-al-2020 eprint = {2101.12046} } +@online{patterson-promonads-2024, + title={Algebras are promonads}, + author={Patterson, Evan}, + year={2024}, + month={1}, + organization={Topos Institute}, + url={https://topos.institute/blog/2024-01-29-algebras-are-promonads/} +} + @article{pollack-2002, author = {Pollack, Robert}, title = {Dependently Typed Records in Type Theory}, diff --git a/rfc/_templates/tikz.tex b/rfc/_templates/tikz.tex index 26fad127a..07b31b253 100644 --- a/rfc/_templates/tikz.tex +++ b/rfc/_templates/tikz.tex @@ -1,7 +1,10 @@ \documentclass[dvisvgm,preview]{standalone} \usepackage{amsmath,amssymb} \usepackage{quiver} + +% Proof trees \usepackage{ebproof} +\ebproofnewstyle{small}{template=\footnotesize$\inserttext$} % WARNING: the macros here are (currently) manually copied from _macros.qmd @@ -19,6 +22,7 @@ % Categories \newcommand{\cat}[1]{\mathsf{#1}} +\newcommand{\Set}{\cat{Set}} % Double categories \newcommand{\dbl}[1]{\mathbb{#1}} @@ -36,9 +40,9 @@ \newcommand{\proTo}{\mathrel{\mkern3mu\vcenter{\hbox{$\shortmid$}}\mkern-10mu{\Rightarrow}}} % Monads -\newcommand{\List}{\mathrm{List}} -\newcommand{\Disc}{\mathop{\mathrm{Disc}}} -\newcommand{\coDisc}{\mathop{\mathrm{coDisc}}} +\newcommand{\List}{\operatorname{List}} +\newcommand{\Disc}{\operatorname{Disc}} +\newcommand{\coDisc}{\operatorname{coDisc}} % Type theory \newcommand{\context}{\mathsf{cx}} diff --git a/rfc/chicago-author-date.csl b/rfc/chicago-author-date.csl index 6dacc76b6..6896c5f64 100644 --- a/rfc/chicago-author-date.csl +++ b/rfc/chicago-author-date.csl @@ -4101,7 +4101,7 @@ - + diff --git a/rfc/filters.lua b/rfc/filters.lua index 11093eda1..6c8886b94 100644 --- a/rfc/filters.lua +++ b/rfc/filters.lua @@ -98,6 +98,9 @@ end local function handle_codeblock(el) local tikz_template = tikz_templates[el.classes[1]] if tikz_template ~= nil then + if FORMAT:match "latex" then + return pandoc.RawBlock("latex", el.text) + end local before = tikz_template[1]:gsub("@OPTIONAL_PREAMBLE", tikz_user_preamble) return pandoc.Div( memoize_svg(el.text, tikz2image(tikz_template), before .. tikz_template[2]), diff --git a/rfc/nonactionable/0005.md b/rfc/nonactionable/0005.md index 4ff12d204..491e7ea83 100644 --- a/rfc/nonactionable/0005.md +++ b/rfc/nonactionable/0005.md @@ -9,8 +9,11 @@ aliases: tikz-preamble: | \DeclareMathOperator{\Mnd}{\textsf{Mnd}} \DeclareMathOperator{\Cat}{\textsf{Cat}} - \DeclareMathOperator{\Set}{\textsf{Set}} + \renewcommand{\Set}{\operatorname{\textsf{Set}}} \DeclareMathOperator{\Maybe}{Maybe} + \DeclareMathOperator{\Psh}{Psh} + \DeclareMathOperator{\eel}{\mathbb{E}l} + \DeclareMathOperator{\Mor}{Mor} \newlength{\ProArLineHeight}\setlength{\ProArLineHeight}{1.25ex} @@ -49,18 +52,17 @@ tikz-preamble: | \draw ({#1}.center) ++(360-\pullbackangle:\pullbackradius) -- +(0,-\pullbackmarkerlength); \draw ({#1}.center) ++(270+\pullbackangle:\pullbackradius) -- +(\pullbackmarkerlength,0); } - --- {{< include ../_macros.qmd >}} -\DeclareMathOperator{\Mnd}{\textsf{Mnd}} -\DeclareMathOperator{\Cat}{\textsf{Cat}} -\DeclareMathOperator{\Set}{\textsf{Set}} -\DeclareMathOperator{\Maybe}{Maybe} -\DeclareMathOperator{\Psh}{Psh} -\DeclareMathOperator{\eel}{\mathbb{E}l} -\DeclareMathOperator{\Mor}{Mor} +\newcommand{\Mnd}{\operatorname{\textsf{Mnd}}} +\newcommand{\Cat}{\operatorname{\textsf{Cat}}} +\renewcommand{\Set}{\operatorname{\textsf{Set}}} +\newcommand{\Maybe}{\operatorname{Maybe}} +\newcommand{\Psh}{\operatorname{Psh}} +\newcommand{\eel}{\operatorname{\mathbb{E}l}} +\newcommand{\Mor}{\operatorname{Mor}} ::: {.callout-warning} ### RFC status