diff --git a/.claude/helpers/merge-claude-settings.sh b/.claude/helpers/merge-claude-settings.sh index 3de8e1a21..8343e15d5 100644 --- a/.claude/helpers/merge-claude-settings.sh +++ b/.claude/helpers/merge-claude-settings.sh @@ -72,6 +72,9 @@ merged_json=$(echo "$parsed_json" | jq -s ' # Collect allow patterns from all files, flatten, and deduplicate allow: ([.[].permissions.allow // [] | .[] ] | unique), + # Collect ask patterns from all files, flatten, and deduplicate + ask: ([.[].permissions.ask // [] | .[] ] | unique), + # Collect deny patterns from all files, flatten, and deduplicate deny: ([.[].permissions.deny // [] | .[] ] | unique) } diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 9904260d7..1d2e81ac2 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -6,7 +6,7 @@ reviews: - path: "**/vendor_files/**" instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it." - path: "**/*.py" - instructions: "Do not express concerns about assert statements being removed by using the -O python flag; we never use that flag. Do not express concerns about ruff rules; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().init() calls; pyright prefers those to be present." + instructions: "Check the `ruff.toml` and `ruff-test.toml` for linting rules we've explicitly disabled and don't suggest changes to please conventions we've disabled. Do not express concerns about ruff rules; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().__init__() calls; pyright prefers those to be present. Do not warn about missing type hints; a pre-commit hook already checks for that." tools: eslint: # when the code contains typescript, eslint will be run by pre-commit, and coderabbit often generates false positives enabled: false diff --git a/.copier-answers.yml b/.copier-answers.yml index 3184e9046..2df81a4f8 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.98 +_commit: v0.0.100 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: A web app that is hosted within a local intranet. Nuxt frontend, python backend, docker-compose diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8ce2709c4..57b930df4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,7 +18,7 @@ "extensions": [ // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.18.1", + "coderabbit.coderabbit-vscode@0.18.3", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", "github.copilot@1.388.0", @@ -61,5 +61,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 63a1f57e # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): aaeff354 # spellchecker:disable-line } diff --git a/.devcontainer/install-ci-tooling.py b/.devcontainer/install-ci-tooling.py index 5c52200cf..569976ed3 100644 --- a/.devcontainer/install-ci-tooling.py +++ b/.devcontainer/install-ci-tooling.py @@ -7,7 +7,7 @@ import tempfile from pathlib import Path -UV_VERSION = "0.10.7" +UV_VERSION = "0.10.8" PNPM_VERSION = "10.30.3" COPIER_VERSION = "==9.12.0" COPIER_TEMPLATE_EXTENSIONS_VERSION = "==0.3.3" diff --git a/.devcontainer/on-create-command.sh b/.devcontainer/on-create-command.sh index cdd3ae803..4383daf27 100644 --- a/.devcontainer/on-create-command.sh +++ b/.devcontainer/on-create-command.sh @@ -12,7 +12,7 @@ repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" mkdir -p "$repo_root/.claude" chmod -R ug+rwX "$repo_root/.claude" chgrp -R 0 "$repo_root/.claude" || true -npm --prefix "$repo_root/.claude" install +npm --prefix "$repo_root/.claude" ci # Install beads for use in Claude planning npm install -g @beads/bd@0.57.0 # no specific reason for this version, just pinning for best practice diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index ba003d740..311fc6895 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -156,7 +156,7 @@ jobs: - name: Upload Docker Image Artifact if: ${{ inputs.save-as-artifact }} - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} path: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82578f407..f78bcc9e2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -150,7 +150,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v5.0.2 + uses: actions/cache@v5.0.3 env: cache-name: cache-pre-commit-hooks with: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 90f6ceaed..ffeb94851 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -59,7 +59,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v5.0.2 + uses: actions/cache@v5.0.3 env: cache-name: cache-pre-commit-hooks with: diff --git a/AGENTS.md b/AGENTS.md index 9b62bfc39..913180b3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ - Don't sort or remove imports manually — pre-commit handles it. - Always include type hints for pyright in Python - Respect the pyright rule reportUnusedCallResult; assign unneeded return values to `_` -- Prefer keyword-only parameters: use `*` in Python signatures and destructured options objects in TypeScript. +- Prefer keyword-only parameters (unless a very clear single-argument function): use `*` in Python signatures and destructured options objects in TypeScript. ## Testing - Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types by default. @@ -12,9 +12,15 @@ - Avoid magic values in comparisons in tests in all languages (like ruff rule PLR2004 specifies) - Prefer using random values in tests rather than arbitrary ones (e.g. the faker library, uuids, random.randint) when possible. +### Python Testing +- When using `mocker.spy` on a class-level method (including inherited ones), the spy records the unbound call, so assertions need `ANY` as the first argument to match self: `spy.assert_called_once_with(ANY, expected_arg)` +- Before writing new mock/spy helpers, check the `tests/unit/` folder for pre-built helpers in files like `fixtures.py` or `*mocks.py` + + ## Tooling - Always use `uv run python` instead of `python3` or `python` when running Python commands. - Check .devcontainer/devcontainer.json for tooling versions (Python, Node, etc.) when reasoning about version-specific stdlib or tooling behavior. +- For frontend work, run commands via `pnpm` scripts from `frontend/package.json` - When running terminal commands, execute exactly one command per tool call. Do not chain commands with &&, ||, ;, or & unless the user explicitly asks for it. Pipes (|) are allowed for output transformation (e.g., head, tail, grep). If two sequential commands are needed, run them in separate tool calls. diff --git a/extensions/context.py b/extensions/context.py index 8d2f6ab30..7aa2ed819 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,7 +10,7 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.10.7" + context["uv_version"] = "0.10.8" context["pnpm_version"] = "10.30.3" context["pre_commit_version"] = "4.5.1" context["pyright_version"] = ">=1.1.408" @@ -33,7 +33,7 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["pyinstaller_version"] = ">=6.19.0" context["setuptools_version"] = "80.7.1" context["strawberry_graphql_version"] = ">=0.298.0" - context["fastapi_version"] = ">=0.129.0" + context["fastapi_version"] = ">=0.135.1" context["fastapi_offline_version"] = ">=1.7.4" context["uvicorn_version"] = ">=0.41.0" context["lab_auto_pulumi_version"] = ">=0.1.18" @@ -51,7 +51,7 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["python_faker_version"] = ">=40.4.0" context["default_node_version"] = "24.11.1" - context["nuxt_ui_version"] = "^4.4.0" + context["nuxt_ui_version"] = "^4.5.1" context["nuxt_version"] = "^4.3.1" context["nuxt_icon_version"] = "^2.2.1" context["typescript_version"] = "^5.9.3" @@ -79,14 +79,14 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["vue_test_utils_version"] = "^2.4.6" context["nuxt_test_utils_version"] = "3.19.1" context["vue_eslint_parser_version"] = "^10.4.0" - context["happy_dom_version"] = "^20.6.3" + context["happy_dom_version"] = "^20.8.3" context["node_kiota_bundle_version"] = "1.0.0-preview.99" context["gha_checkout"] = "v6.0.2" context["gha_setup_python"] = "v6.2.0" - context["gha_cache"] = "v5.0.2" - context["gha_upload_artifact"] = "v6.0.0" - context["gha_download_artifact"] = "v7.0.0" + context["gha_cache"] = "v5.0.3" + context["gha_upload_artifact"] = "v7.0.0" + context["gha_download_artifact"] = "v8.0.0" context["gha_github_script"] = "v7.0.1" context["gha_setup_buildx"] = "v3.11.1" context["buildx_version"] = "v0.27.0" diff --git a/template/.claude/helpers/merge-claude-settings.sh b/template/.claude/helpers/merge-claude-settings.sh index 3de8e1a21..8343e15d5 100644 --- a/template/.claude/helpers/merge-claude-settings.sh +++ b/template/.claude/helpers/merge-claude-settings.sh @@ -72,6 +72,9 @@ merged_json=$(echo "$parsed_json" | jq -s ' # Collect allow patterns from all files, flatten, and deduplicate allow: ([.[].permissions.allow // [] | .[] ] | unique), + # Collect ask patterns from all files, flatten, and deduplicate + ask: ([.[].permissions.ask // [] | .[] ] | unique), + # Collect deny patterns from all files, flatten, and deduplicate deny: ([.[].permissions.deny // [] | .[] ] | unique) } diff --git a/template/.coderabbit.yaml b/template/.coderabbit.yaml index bba651d5b..dd08ed4c8 100644 --- a/template/.coderabbit.yaml +++ b/template/.coderabbit.yaml @@ -12,7 +12,7 @@ reviews: - path: "**/vendor_files/**" instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it." - path: "**/*.py" - instructions: "Do not express concerns about assert statements being removed by using the -O python flag; we never use that flag. Do not express concerns about ruff rules; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().init() calls; pyright prefers those to be present." + instructions: "Check the `ruff.toml` and `ruff-test.toml` for linting rules we've explicitly disabled and don't suggest changes to please conventions we've disabled. Do not express concerns about ruff rules; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().__init__() calls; pyright prefers those to be present. Do not warn about missing type hints; a pre-commit hook already checks for that." tools: eslint: # when the code contains typescript, eslint will be run by pre-commit, and coderabbit often generates false positives enabled: false diff --git a/template/.devcontainer/devcontainer.json.jinja b/template/.devcontainer/devcontainer.json.jinja index 8c8cab298..7cb3af6ef 100644 --- a/template/.devcontainer/devcontainer.json.jinja +++ b/template/.devcontainer/devcontainer.json.jinja @@ -36,7 +36,7 @@ "-AmazonWebServices.aws-toolkit-vscode", // the AWS CLI feature installs this automatically, but it's causing problems in VS Code{% endraw %}{% endif %}{% raw %} // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.18.1", + "coderabbit.coderabbit-vscode@0.18.3", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", "github.copilot@1.388.0", diff --git a/template/.devcontainer/on-create-command.sh.jinja b/template/.devcontainer/on-create-command.sh.jinja index 4614face3..199332787 100644 --- a/template/.devcontainer/on-create-command.sh.jinja +++ b/template/.devcontainer/on-create-command.sh.jinja @@ -12,7 +12,7 @@ repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" mkdir -p "$repo_root/.claude" chmod -R ug+rwX "$repo_root/.claude" chgrp -R 0 "$repo_root/.claude" || true -npm --prefix "$repo_root/.claude" install +npm --prefix "$repo_root/.claude" ci # Install beads for use in Claude planning npm install -g @beads/bd@0.57.0 # no specific reason for this version, just pinning for best practice{% endraw %}{% endif %}{% raw %} diff --git a/template/.github/workflows/pre-commit.yaml b/template/.github/workflows/pre-commit.yaml index 90f6ceaed..ffeb94851 100644 --- a/template/.github/workflows/pre-commit.yaml +++ b/template/.github/workflows/pre-commit.yaml @@ -59,7 +59,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v5.0.2 + uses: actions/cache@v5.0.3 env: cache-name: cache-pre-commit-hooks with: diff --git a/template/AGENTS.md b/template/AGENTS.md index 9b62bfc39..ab96d35e8 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -4,7 +4,7 @@ - Don't sort or remove imports manually — pre-commit handles it. - Always include type hints for pyright in Python - Respect the pyright rule reportUnusedCallResult; assign unneeded return values to `_` -- Prefer keyword-only parameters: use `*` in Python signatures and destructured options objects in TypeScript. +- Prefer keyword-only parameters (unless a very clear single-argument function): use `*` in Python signatures and destructured options objects in TypeScript. ## Testing - Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types by default. @@ -12,9 +12,15 @@ - Avoid magic values in comparisons in tests in all languages (like ruff rule PLR2004 specifies) - Prefer using random values in tests rather than arbitrary ones (e.g. the faker library, uuids, random.randint) when possible. +### Python Testing +- When using `mocker.spy` on a class-level method (including inherited ones), the spy records the unbound call, so assertions need `ANY` as the first argument to match self: `spy.assert_called_once_with(ANY, expected_arg)` +- Before writing new mock/spy helpers, check the `tests/unit/` folder for pre-built helpers in files like `fixtures.py` or `*mocks.py` + + ## Tooling - Always use `uv run python` instead of `python3` or `python` when running Python commands. - Check .devcontainer/devcontainer.json for tooling versions (Python, Node, etc.) when reasoning about version-specific stdlib or tooling behavior. +- For frontend work, run commands via `pnpm` scripts from `frontend/package.json` - When running terminal commands, execute exactly one command per tool call. Do not chain commands with &&, ||, ;, or & unless the user explicitly asks for it. Pipes (|) are allowed for output transformation (e.g., head, tail, grep). If two sequential commands are needed, run them in separate tool calls. @@ -104,3 +110,8 @@ bd export -o .claude/.beads/issues-dump.jsonl For more details, see README.md and docs/QUICKSTART.md. + +## Project Structure +- This is a statically generated frontend---using the Nuxt and @nuxt/ui frameworks---meant to operate in an air-gapped environment. That code is in the `frontend/` directory. +- There may also be a backend that the frontend interacts with, in `backend/`. If present, it will be a Python FastAPI uvicorn server. +- Kiota is used for codegen from the OpenAPI schema diff --git a/template/frontend/nuxt.config.ts.jinja b/template/frontend/nuxt.config.ts.jinja index a466170f2..d04c1abfe 100644 --- a/template/frontend/nuxt.config.ts.jinja +++ b/template/frontend/nuxt.config.ts.jinja @@ -3,7 +3,7 @@ import { defineNuxtConfig } from "nuxt/config"; export default defineNuxtConfig({ compatibilityDate: "2024-11-01", future: { - compatibilityVersion: 4, + compatibilityVersion: 4, // as of 2026-03-05, @nuxt/icon v2.1.1 was not compatible with v5, so waiting a bit longer }, devtools: { enabled: process.env.NODE_ENV !== "test" }, telemetry: process.env.NODE_ENV !== "test", @@ -39,6 +39,11 @@ export default defineNuxtConfig({ css: ["~/assets/css/main.css"], experimental: { appManifest: false }, // https://github.com/nuxt/nuxt/issues/30461#issuecomment-2572616714 nitro: { + esbuild: { + options: { + target: "es2024", // no specific reason for pinning to this version, but the default for nitro was 2019, so using something a bit more modern + }, + }, prerender: { concurrency: 1, // lower the concurrency to not be such a memory hog interval: 200, // ms pause between batches – lets the Garbage Collector catch up