mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
gatelock 0.4.0 (pinned here) stops the viewport following the app's own focus_set(): on 2026-08-03 that moved the screen locker's lock screen to mid-content and back with nobody touching the keyboard, and this gate uses the same viewport. Scrolling now happens only on the user's own keypress, wheel or click. That makes fitting the screen the gate's own responsibility rather than something a scrollbar covers for, and with a day of meals logged this one did not: 770px of content in a 739px pane. Trimmed the vertical rhythm (the title is one step down the scale, and the gaps around the description, suggestions, macros and footer are one step tighter) for 718px, with room for the dashboard to keep growing. Its layout test now requires the gate to *fit* rather than merely be reachable by scrolling, empty and populated. Dropped the 1024x600 tier: no machine runs it, this gate cannot reach it without dropping content, and a target nobody uses is a constraint that only ever costs. The probe presses Tab before moving focus, because a bare focus_set() no longer scrolls -- it would have been testing a path no user can take. The Tk fakes learned pack_forget(), which the viewport calls to take the scrollbar down whenever the content fits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsXnTHyuuCUZsqetTaajcF
194 lines
5.5 KiB
TOML
194 lines
5.5 KiB
TOML
[project]
|
|
name = "diet-guard"
|
|
version = "1.0.0"
|
|
description = "Tkinter/systemd log-to-unlock meal gate with calorie/macro budget tracking"
|
|
requires-python = ">=3.10"
|
|
dependencies = [
|
|
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.0#subdirectory=gatelock",
|
|
"crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.1.0#subdirectory=crdt-sync",
|
|
"requests>=2.31.0",
|
|
]
|
|
|
|
# Optional extras. The `mcp` group is ONLY needed by the MCP server
|
|
# (diet_guard._mcp), which Claude Code spawns via a dedicated venv (see
|
|
# scripts/setup_mcp.sh / .mcp.json). It is deliberately NOT a core dependency
|
|
# so the systemd/CLI system-python path stays dependency-light and never has
|
|
# to import the MCP SDK.
|
|
[project.optional-dependencies]
|
|
mcp = ["mcp>=1.9"]
|
|
|
|
# Without this, setuptools' flat-layout auto-discovery treats the Flutter
|
|
# app/ directory as a second top-level package and refuses to build.
|
|
[tool.setuptools.packages.find]
|
|
include = ["diet_guard*"]
|
|
|
|
[tool.ruff]
|
|
target-version = "py310"
|
|
include = ["*.py", "**/*.py"]
|
|
exclude = [".git", ".venv", "__pycache__", "build", "dist", ".eggs"]
|
|
|
|
[tool.ruff.lint]
|
|
select = ["ALL"]
|
|
ignore = [
|
|
"D203",
|
|
"D213",
|
|
"COM812",
|
|
"ISC001",
|
|
"S603",
|
|
]
|
|
fixable = ["ALL"]
|
|
unfixable = []
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"**/tests/**/*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "S107", "SLF001"]
|
|
"**/test_*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "S107", "SLF001"]
|
|
|
|
[tool.ruff.lint.pydocstyle]
|
|
convention = "google"
|
|
|
|
[tool.ruff.lint.isort]
|
|
force-single-line = false
|
|
force-sort-within-sections = true
|
|
known-first-party = ["diet_guard"]
|
|
|
|
[tool.ruff.lint.flake8-quotes]
|
|
docstring-quotes = "double"
|
|
inline-quotes = "double"
|
|
|
|
[tool.ruff.lint.flake8-tidy-imports]
|
|
ban-relative-imports = "all"
|
|
|
|
[tool.ruff.format]
|
|
quote-style = "double"
|
|
indent-style = "space"
|
|
skip-magic-trailing-comma = false
|
|
line-ending = "auto"
|
|
docstring-code-format = true
|
|
|
|
[tool.mypy]
|
|
python_version = "3.10"
|
|
strict = true
|
|
warn_return_any = true
|
|
warn_unused_configs = true
|
|
disallow_untyped_defs = true
|
|
disallow_incomplete_defs = true
|
|
check_untyped_defs = true
|
|
disallow_untyped_decorators = true
|
|
no_implicit_optional = true
|
|
warn_redundant_casts = true
|
|
warn_unused_ignores = true
|
|
warn_no_return = true
|
|
warn_unreachable = true
|
|
disallow_any_unimported = true
|
|
disallow_any_explicit = false
|
|
disallow_any_generics = true
|
|
disallow_subclassing_any = true
|
|
strict_equality = true
|
|
extra_checks = true
|
|
ignore_missing_imports = true
|
|
show_error_codes = true
|
|
color_output = true
|
|
exclude = [".venv/"]
|
|
|
|
[tool.pylint.main]
|
|
analyse-fallback-blocks = true
|
|
persistent = true
|
|
jobs = 0
|
|
py-version = "3.10"
|
|
# "tests" is a basename match: test suites intentionally use patterns
|
|
# (protected-access, magic-value comparisons) that don't apply to source
|
|
# code, matching testsAndMisc's pylint scope (tests are linted by ruff, not
|
|
# pylint there either).
|
|
ignore = [".venv", "__pycache__", "tests"]
|
|
ignore-patterns = [".*\\.pyi$"]
|
|
|
|
[tool.pylint.messages_control]
|
|
# Enable all checks by disabling disable
|
|
enable = "all"
|
|
# Globally disabled checks. Each is either a stylistic preference that conflicts
|
|
# with deliberate, clearer code, or a structural false positive that cannot be
|
|
# rewritten without harming readability. Everything else stays at max strictness.
|
|
disable = [
|
|
# use-implicit-booleaness family (C1803/C1804/C1805): pylint wants
|
|
# `not x` / `not s` instead of `x == 0` / `s == ""`. Explicit comparisons
|
|
# against 0 and "" state numeric/string intent more clearly than truthiness
|
|
# (and are not equivalent when the value may be None), so we keep them.
|
|
"use-implicit-booleaness-not-comparison",
|
|
"use-implicit-booleaness-not-comparison-to-string",
|
|
"use-implicit-booleaness-not-comparison-to-zero",
|
|
# consider-using-with (R1732): several subprocess.Popen calls are
|
|
# intentionally fire-and-forget — the process must outlive the calling
|
|
# scope and is polled/killed later, so a `with` block is wrong here.
|
|
"consider-using-with",
|
|
]
|
|
|
|
[tool.pylint.design]
|
|
min-public-methods = 0
|
|
max-module-lines = 1000
|
|
max-attributes = 10
|
|
|
|
[tool.pylint.similarities]
|
|
# _foodbank's BankRecord and _mcp's response dict both list the same five
|
|
# nutrition fields (kcal/protein_g/carbs_g/fat_g/grams) because they mirror the
|
|
# same domain schema, then diverge (desc/count vs source). Raise the threshold
|
|
# so pylint doesn't flag this intentional structural overlap as duplicate-code.
|
|
min-similarity-lines = 8
|
|
|
|
[tool.pylint.typecheck]
|
|
generated-members = [
|
|
".*\\.assert_called_once_with",
|
|
".*\\.assert_called_once",
|
|
".*\\.assert_called",
|
|
".*\\.assert_not_called",
|
|
".*\\.assert_any_call",
|
|
".*\\.call_args",
|
|
".*\\.call_args_list",
|
|
".*\\.call_count",
|
|
]
|
|
|
|
[tool.bandit]
|
|
exclude_dirs = ["tests", ".venv"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["diet_guard/tests"]
|
|
python_files = ["test_*.py", "*_test.py"]
|
|
python_classes = ["Test*"]
|
|
python_functions = ["test_*"]
|
|
addopts = [
|
|
"-v",
|
|
"--strict-markers",
|
|
"--strict-config",
|
|
"-ra",
|
|
"--cov=diet_guard",
|
|
"--cov-branch",
|
|
"--cov-report=term-missing",
|
|
"--cov-report=lcov",
|
|
]
|
|
filterwarnings = [
|
|
"error",
|
|
"ignore::DeprecationWarning",
|
|
"default::pytest.PytestUnraisableExceptionWarning",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["diet_guard"]
|
|
branch = true
|
|
omit = [
|
|
"*/__pycache__/*",
|
|
"*/tests/*",
|
|
"*/.venv/*",
|
|
]
|
|
|
|
[tool.coverage.report]
|
|
fail_under = 100
|
|
show_missing = true
|
|
skip_covered = false
|
|
exclude_lines = [
|
|
"pragma: no cover",
|
|
"raise NotImplementedError",
|
|
"raise AssertionError",
|
|
"if TYPE_CHECKING:",
|
|
'if __name__ == "__main__":',
|
|
]
|
|
partial_branches = ["pragma: no branch"]
|