stkit 17.2.3__tar.gz → 17.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stkit-17.2.3 → stkit-17.2.5}/CHANGELOG.md +18 -0
- {stkit-17.2.3 → stkit-17.2.5}/PKG-INFO +1 -1
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/app.py +2 -1
- {stkit-17.2.3 → stkit-17.2.5}/pyproject.toml +1 -1
- {stkit-17.2.3 → stkit-17.2.5}/stk/__init__.py +1 -1
- {stkit-17.2.3 → stkit-17.2.5}/stk/app.py +0 -2
- {stkit-17.2.3 → stkit-17.2.5}/stk/checks.py +22 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/copy_rules.py +3 -1
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/smoke.py +12 -3
- {stkit-17.2.3 → stkit-17.2.5}/stk/notification/__init__.py +13 -1
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/__app__/app.py +2 -1
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/layout.html +3 -1
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_copy_rules.py +26 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_notifications.py +2 -1
- stkit-17.2.5/tests/test_route_collisions.py +41 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_scaffolder.py +1 -1
- {stkit-17.2.3 → stkit-17.2.5}/.gitignore +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/LICENSE +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/README.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/SECURITY.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/.env-sample +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/.gitignore +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/README.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/migrations/364232ae77d5_add_note.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/note/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/note/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/note/templates/note/index.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/note/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/settings.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/minimal/templates/layout.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/pyproject.toml +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/tests/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/tests/test_app.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/examples/minimal/uv.lock +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/__main__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/agent_login.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/agent.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/base.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/database.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/develop.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/doctor.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/main.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/reports.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/users.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/cli/watch.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/extensions.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/files/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/files/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/files/store.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/files/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/mcp/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/mcp/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/mcp/server.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/mcp/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/env.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/script.py.mako +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0001_initial_schema.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0002_jobs.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0003_files.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0004_notifications.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0005_mcp_tokens.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/migrations/versions/stk_0006_stk_mcp_token.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/notification/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/notification/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/portal/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/public/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/public/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/public/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/CLAUDE.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/Dockerfile +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/README.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/__app__/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/__app__/settings.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/claude/skills/stk-api/SKILL.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/docker-compose.yml +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/dockerignore +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/env-sample +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/github/workflows/checks.yml +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/gitignore +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/nginx/nginx.conf +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/nginx/stk.conf +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/postgres/init-db.sh +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/pyproject.toml +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/tests/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/app/tests/test_app.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/generator.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/scaffold/templates.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/security.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/settings.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/shell.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/VERSIONS.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/css/app.css +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/css/layout.css +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/css/vuetify.min.css +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.animals.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.arrows-1.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.arrows-2.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.badges.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.brand-1.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.brand-2.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.buildings.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.charts.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.communication.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.computers.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.core.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.currencies.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.database.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.design-1.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.design-2.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.development.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.devices-1.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.devices-2.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.document.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.e-commerce.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.electrical.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.extensions.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.food.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.games.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.gender.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.gestures.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.health.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.laundry.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.letters.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.logic.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.map.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.math.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.media.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.mood.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.nature.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.numbers.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.photography.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.shapes.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.sport.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.symbols.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.system-1.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.system-2.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.system-3.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.text.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.vehicles.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.version-control.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.weather.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/fonts/tabler-icons.zodiac.woff2 +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/icons/tabler-icons.min.css +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/img/auth-bg.webp +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/img/favicon.ico +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/img/stk.svg +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/axios.min.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/NotificationDropdown.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/ThemeSwitcher.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/TransitionExpand.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/VerticalNavGroup.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/VerticalNavLink.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/VerticalNavSectionTitle.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/components/index.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/config.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/vue.min.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/js/vuetify.min.js +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/static/robots.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/tasks/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/tasks/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/401.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/404.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/500.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/_brand.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/auth_layout.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/cms/activities.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/cms/jobs.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/cms/roles.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/cms/users.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/dashboard.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/index.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/login-layout.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/mcp/tokens.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/_macros.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/_messages.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/change_password.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/confirm_email.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/change_notice.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/change_notice.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/confirmation_instructions.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/confirmation_instructions.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/login_instructions.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/login_instructions.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/reset_instructions.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/reset_instructions.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/reset_notice.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/reset_notice.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/welcome.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/email/welcome.txt +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/forgot_password.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/login_user.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/mf_recovery.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/mf_recovery_codes.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/register_user.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/reset_password.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/send_confirmation.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/send_login.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/two_factor_select.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/two_factor_setup.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/two_factor_verify_code.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/wan_register.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/wan_signin.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/templates/security/wan_verify.html +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/user/__init__.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/user/forms.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/user/models.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/user/views.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/utils/email.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/utils/netguard.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/utils/ratelimit.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/stk/websocket.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_agent_operability.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_app_index.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_branding.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_builder_ux.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_cli.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_composition.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_csrf_exempt.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_error_handler.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_files.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_jobs.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_mcp.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_mcp_migration.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_migration_drift.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_netguard.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_production_security.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_revision_imports.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_security_compatibility.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_shell.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_smoke_pages.py +0 -0
- {stkit-17.2.3 → stkit-17.2.5}/tests/test_static_versioning.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v17.2.5 (2026-10-31)
|
|
4
|
+
|
|
5
|
+
No migration required.
|
|
6
|
+
|
|
7
|
+
- `stk smoke` crashed on any page with three or more like SVG shapes side by side, such as a chart's paths or an icon's circles: the repeated-row rule read `innerText`, which SVG elements lack. The rule now looks at HTML elements only. A page smoke cannot read now fails that page with the reason, instead of stopping the whole run (Build Loop).
|
|
8
|
+
|
|
9
|
+
## v17.2.4 (2026-10-31)
|
|
10
|
+
|
|
11
|
+
No migration required.
|
|
12
|
+
|
|
13
|
+
- Notifications are opt-in, like uploads and MCP: add `stk.notification` to the app's extensions for the `/api/notifications` routes and the layout's bell. From 17.2.0 to 17.2.3 stk registered those routes in every app, and because the first matching route answers, an app's own `/api/notifications` was silently never reached. `notify()` still stores rows without it (Background Work).
|
|
14
|
+
- `stk verify` fails when two endpoints register the same path and method, naming both, so a shadowed route can no longer ship silently.
|
|
15
|
+
- Apps made by `stk init`, and the example app, list `stk.notification`.
|
|
16
|
+
|
|
17
|
+
### Upgrade considerations
|
|
18
|
+
|
|
19
|
+
- An app that uses stk's bell adds `import stk.notification` and puts `stk.notification` in its `extensions` list, or the bell disappears and `/api/notifications` returns 404.
|
|
20
|
+
|
|
3
21
|
## v17.2.3 (2026-10-31)
|
|
4
22
|
|
|
5
23
|
Run `uv run stk db upgrade`.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import stk.notification
|
|
1
2
|
from stk import Stk
|
|
2
3
|
|
|
3
4
|
from minimal import note
|
|
4
5
|
from minimal.settings import Config
|
|
5
6
|
|
|
6
7
|
# Each extension is a package with init_app(app); order is precedence.
|
|
7
|
-
extensions = [note]
|
|
8
|
+
extensions = [stk.notification, note]
|
|
8
9
|
|
|
9
10
|
app = Stk(__name__, config=Config, extensions=extensions)
|
|
@@ -27,7 +27,6 @@ from werkzeug.security import safe_join
|
|
|
27
27
|
import stk.extensions as ext
|
|
28
28
|
from stk.agent_login import agent_login_enabled, bp_agent_login
|
|
29
29
|
from stk.extensions import session
|
|
30
|
-
from stk.notification.views import bp_notification
|
|
31
30
|
from stk.portal.views import portal
|
|
32
31
|
from stk.public.views import public
|
|
33
32
|
from stk.settings import Config
|
|
@@ -206,7 +205,6 @@ def register_blueprints(app):
|
|
|
206
205
|
app.register_blueprint(bp_user)
|
|
207
206
|
app.register_blueprint(public)
|
|
208
207
|
app.register_blueprint(portal)
|
|
209
|
-
app.register_blueprint(bp_notification)
|
|
210
208
|
app.register_blueprint(ws_bp)
|
|
211
209
|
if agent_login_enabled(app):
|
|
212
210
|
app.register_blueprint(bp_agent_login)
|
|
@@ -295,6 +295,28 @@ def check_admin_reset_route(app):
|
|
|
295
295
|
)
|
|
296
296
|
|
|
297
297
|
|
|
298
|
+
@check("No route is registered twice")
|
|
299
|
+
def check_unique_routes(app):
|
|
300
|
+
# Werkzeug answers with the first matching rule, so an app route that repeats
|
|
301
|
+
# a core one (or another extension's) is never reached, with no error at all.
|
|
302
|
+
owners = {}
|
|
303
|
+
for rule in app.url_map.iter_rules():
|
|
304
|
+
# stk's own blueprints share /static on purpose: one folder, one answer.
|
|
305
|
+
if rule.endpoint == "static" or rule.endpoint.endswith(".static"):
|
|
306
|
+
continue
|
|
307
|
+
for method in rule.methods - {"HEAD", "OPTIONS"}:
|
|
308
|
+
owners.setdefault((method, rule.rule), []).append(rule.endpoint)
|
|
309
|
+
clashes = [
|
|
310
|
+
f"{method} {path}: {' and '.join(endpoints)}"
|
|
311
|
+
for (method, path), endpoints in sorted(owners.items())
|
|
312
|
+
if len(endpoints) > 1
|
|
313
|
+
]
|
|
314
|
+
assert not clashes, (
|
|
315
|
+
"only the first endpoint ever answers; rename one route or drop the"
|
|
316
|
+
" extension that adds it:\n " + "\n ".join(clashes)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
298
320
|
@check("GET / answers 200, after any redirects")
|
|
299
321
|
async def check_http_index(app):
|
|
300
322
|
# An app may send / to its sign-in or home page; what it lands on must work.
|
|
@@ -15,8 +15,10 @@ VISIBLE_TEXT_JS = """() => {
|
|
|
15
15
|
}"""
|
|
16
16
|
|
|
17
17
|
# Visible texts of sibling elements that share a tag and classes: rows of a list.
|
|
18
|
+
# Only HTML elements: SVG shapes (a chart's paths, an icon's circles) repeat by
|
|
19
|
+
# design and have no innerText.
|
|
18
20
|
ROWS_JS = """() => {
|
|
19
|
-
const shown = el => el.getClientRects().length > 0;
|
|
21
|
+
const shown = el => el instanceof HTMLElement && el.getClientRects().length > 0;
|
|
20
22
|
const sig = k => k.tagName + '.' + [...k.classList]
|
|
21
23
|
.filter(c => !/active|selected|hover|focus|open|current/.test(c)).sort().join('.');
|
|
22
24
|
const out = [];
|
|
@@ -303,9 +303,18 @@ async def _visit_smoke_page(context, base_url, name, path, screenshot_path=None)
|
|
|
303
303
|
page.on("pageerror", _record_page_error)
|
|
304
304
|
page.on("requestfailed", _record_failed_request)
|
|
305
305
|
response = await page.goto(f"{base_url}{path}", wait_until="networkidle")
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
from playwright.async_api import Error as PlaywrightError
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
low_contrast = await page.evaluate(CONTRAST_JS)
|
|
310
|
+
text = await page.evaluate(VISIBLE_TEXT_JS)
|
|
311
|
+
rows = await page.evaluate(ROWS_JS)
|
|
312
|
+
except PlaywrightError as exc:
|
|
313
|
+
# One page smoke cannot read fails that page, not the whole run.
|
|
314
|
+
console_entries.append(
|
|
315
|
+
{"type": "pageerror", "text": f"smoke could not read the page: {exc}"}
|
|
316
|
+
)
|
|
317
|
+
low_contrast, text, rows = [], "", []
|
|
309
318
|
if screenshot_path is not None:
|
|
310
319
|
screenshot_path.parent.mkdir(parents=True, exist_ok=True)
|
|
311
320
|
await page.screenshot(path=str(screenshot_path), full_page=True)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Stored, per-user notifications. The row is the truth; the socket push is a nudge.
|
|
1
|
+
"""Stored, per-user notifications. The row is the truth; the socket push is a nudge.
|
|
2
|
+
|
|
3
|
+
Opt in with `extensions=[stk.notification, ...]` for the `/api/notifications`
|
|
4
|
+
routes and the layout's bell; `notify()` stores rows either way.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import stk.extensions as ext
|
|
4
8
|
from stk.notification.models import Notification
|
|
@@ -27,3 +31,11 @@ async def notify(user_id, title, link=None, kind=None, body=None):
|
|
|
27
31
|
await db.commit()
|
|
28
32
|
await broadcast({"type": "notification", **row.to_dict()}, user_id)
|
|
29
33
|
return row
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def init_app(app):
|
|
37
|
+
from stk.notification.views import bp_notification
|
|
38
|
+
|
|
39
|
+
app.extensions["stk.notification"] = True
|
|
40
|
+
app.jinja_env.globals["stk_notifications"] = True
|
|
41
|
+
app.register_blueprint(bp_notification)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import stk.notification
|
|
1
2
|
from stk import Stk
|
|
2
3
|
|
|
3
4
|
from __app__.settings import Config
|
|
4
5
|
|
|
5
6
|
# Each extension is a package with init_app(app); order is precedence.
|
|
6
|
-
extensions = []
|
|
7
|
+
extensions = [stk.notification]
|
|
7
8
|
|
|
8
9
|
app = Stk(__name__, config=Config, extensions=extensions)
|
|
@@ -48,8 +48,10 @@
|
|
|
48
48
|
<!-- Theme Switcher -->
|
|
49
49
|
<theme-switcher></theme-switcher>
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
{% if stk_notifications %}
|
|
52
|
+
<!-- Notifications: only with stk.notification in the app's extensions -->
|
|
52
53
|
<notification-dropdown ref="notifications"></notification-dropdown>
|
|
54
|
+
{% endif %}
|
|
53
55
|
|
|
54
56
|
<!-- Connection Status -->
|
|
55
57
|
<v-tooltip :text="wsConnected ? 'Real-time connected' : 'Reconnecting...'" location="bottom">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
3
|
from stk.cli.copy_rules import (
|
|
4
|
+
ROWS_JS,
|
|
4
5
|
broken_value,
|
|
5
6
|
copy_findings,
|
|
6
7
|
hex_id,
|
|
@@ -110,3 +111,28 @@ class SmokeCopyReportTests(unittest.TestCase):
|
|
|
110
111
|
|
|
111
112
|
if __name__ == "__main__":
|
|
112
113
|
unittest.main()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class RowsInBrowserTest(unittest.IsolatedAsyncioTestCase):
|
|
117
|
+
"""ROWS_JS runs in a real page: SVG shapes have no innerText."""
|
|
118
|
+
|
|
119
|
+
async def test_repeated_svg_shapes_are_skipped_and_html_rows_kept(self):
|
|
120
|
+
try:
|
|
121
|
+
from playwright.async_api import async_playwright
|
|
122
|
+
|
|
123
|
+
playwright = await async_playwright().start()
|
|
124
|
+
browser = await playwright.chromium.launch()
|
|
125
|
+
except Exception as exc: # no Playwright or no browser installed
|
|
126
|
+
self.skipTest(f"chromium unavailable: {exc}")
|
|
127
|
+
try:
|
|
128
|
+
page = await browser.new_page()
|
|
129
|
+
await page.set_content(
|
|
130
|
+
"<svg width='90' height='30'><circle cx='10' cy='10' r='5'/>"
|
|
131
|
+
"<circle cx='30' cy='10' r='5'/><circle cx='50' cy='10' r='5'/></svg>"
|
|
132
|
+
"<ul><li>Alpha</li><li>Beta</li><li>Gamma</li></ul>"
|
|
133
|
+
)
|
|
134
|
+
rows = await page.evaluate(ROWS_JS)
|
|
135
|
+
finally:
|
|
136
|
+
await browser.close()
|
|
137
|
+
await playwright.stop()
|
|
138
|
+
self.assertEqual(rows, [["Alpha", "Beta", "Gamma"]])
|
|
@@ -5,6 +5,7 @@ import unittest
|
|
|
5
5
|
from quart_security import hash_password
|
|
6
6
|
|
|
7
7
|
import stk.extensions as ext
|
|
8
|
+
import stk.notification
|
|
8
9
|
from stk.app import Stk
|
|
9
10
|
from stk.extensions import Base
|
|
10
11
|
from stk.notification import notify
|
|
@@ -14,7 +15,7 @@ from stk.user.models import User
|
|
|
14
15
|
|
|
15
16
|
class NotificationTests(unittest.IsolatedAsyncioTestCase):
|
|
16
17
|
async def asyncSetUp(self):
|
|
17
|
-
self.app = Stk(__name__, config=TestingConfig)
|
|
18
|
+
self.app = Stk(__name__, config=TestingConfig, extensions=[stk.notification])
|
|
18
19
|
async with ext.engine.begin() as conn:
|
|
19
20
|
await conn.run_sync(Base.metadata.create_all)
|
|
20
21
|
async with ext.async_session_factory() as db:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""An app route that repeats a core one is never reached; verify must say so."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import io
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from quart import Blueprint
|
|
8
|
+
|
|
9
|
+
import stk.notification
|
|
10
|
+
from stk import checks
|
|
11
|
+
from stk.app import Stk
|
|
12
|
+
from stk.checks import check_unique_routes
|
|
13
|
+
from stk.settings import TestingConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RouteCollisionTest(unittest.IsolatedAsyncioTestCase):
|
|
17
|
+
async def test_core_routes_are_unique(self):
|
|
18
|
+
app = Stk(__name__, config=TestingConfig, extensions=[stk.notification])
|
|
19
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
20
|
+
self.assertTrue(await check_unique_routes(app))
|
|
21
|
+
|
|
22
|
+
async def test_an_app_route_shadowed_by_a_core_route_fails(self):
|
|
23
|
+
app = Stk(__name__, config=TestingConfig, extensions=[stk.notification])
|
|
24
|
+
inbox = Blueprint("inbox", __name__)
|
|
25
|
+
|
|
26
|
+
@inbox.get("/api/notifications")
|
|
27
|
+
async def own_notifications():
|
|
28
|
+
return {"items": []}
|
|
29
|
+
|
|
30
|
+
app.register_blueprint(inbox)
|
|
31
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
32
|
+
self.assertFalse(await check_unique_routes(app))
|
|
33
|
+
detail = checks.RESULTS[-1]["detail"]
|
|
34
|
+
self.assertIn("GET /api/notifications", detail)
|
|
35
|
+
self.assertIn("inbox.own_notifications", detail)
|
|
36
|
+
|
|
37
|
+
async def test_without_stk_notification_the_app_owns_the_path(self):
|
|
38
|
+
app = Stk(__name__, config=TestingConfig)
|
|
39
|
+
rules = {r.rule for r in app.url_map.iter_rules()}
|
|
40
|
+
self.assertNotIn("/api/notifications", rules)
|
|
41
|
+
self.assertNotIn("stk_notifications", app.jinja_env.globals)
|
|
@@ -321,7 +321,7 @@ class InitAndNewTests(unittest.TestCase):
|
|
|
321
321
|
ast.parse((self.app / "acme" / "widget" / name).read_text())
|
|
322
322
|
app_module = (self.app / "acme" / "app.py").read_text()
|
|
323
323
|
self.assertIn("from acme import gadget, widget", app_module)
|
|
324
|
-
self.assertIn("extensions = [widget, gadget]", app_module)
|
|
324
|
+
self.assertIn("extensions = [stk.notification, widget, gadget]", app_module)
|
|
325
325
|
|
|
326
326
|
def test_new_starts_the_app_lineage_after_stk(self):
|
|
327
327
|
(revision,) = (self.app / "acme" / "migrations").glob("*.py")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|