mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
app_icons: daily_limits gets an hourglass in the shared family colours
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The daily-limits phone app's launcher icon: charcoal #211D1B field, hourglass glyph in the shared accent #B8862E. Additive only, so every other app's icon is byte-identical. A registry invariant now refuses a new app that reuses another app's glyph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AcxgxMaQHMSGWzLRpNSkuP
This commit is contained in:
parent
30721bff7e
commit
d792c081e4
@ -0,0 +1,15 @@
|
||||
{
|
||||
"title": "app_icons: daily_limits gets the hourglass glyph in the shared family",
|
||||
"objective": "The daily-limits phone app ships a family launcher icon (charcoal #211D1B field, shared accent #B8862E) instead of the stock Flutter icon, with no change to any other app's icon.",
|
||||
"acceptance_criteria": [
|
||||
"`generate --app daily_limits --android` writes assets/icon and the Android mipmaps for ~/src/daily-limits/app",
|
||||
"The registry keeps one accent (#B8862E) and one field (style.BACKGROUND) for every app",
|
||||
"The diff to python_pkg/app_icons is additive only, so every other app renders byte-identically",
|
||||
"python_pkg/app_icons tests pass at 100% line and branch coverage; pre-commit is clean"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"A per-app background or accent override (decided 2026-10-09: the generator's shared colours are right)",
|
||||
"Committing the daily-limits app itself"
|
||||
],
|
||||
"verifier": ".venv/bin/python -m pytest -q python_pkg/app_icons --cov=python_pkg/app_icons --cov-branch; pre-commit run"
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
{
|
||||
"intent": "Give the daily-limits phone app an on-style launcher icon: an hourglass (a daily allowance that runs out) in the shared accent on the family charcoal.",
|
||||
"scope": [
|
||||
"python_pkg/app_icons/_glyph_art_more.py, glyphs.py: the `hourglass` glyph",
|
||||
"python_pkg/app_icons/apps.py: the `daily_limits` registry entry",
|
||||
"python_pkg/app_icons/tests/test_registry.py: no glyph reused across apps; hourglass sand takes the accent marker"
|
||||
],
|
||||
"changes": [
|
||||
"New `hourglass` glyph: capped bars, crossed sides, sand filled in the accent so the monochrome layer recolours it",
|
||||
"daily_limits registered with accent #B8862E and the default style.BACKGROUND field",
|
||||
"Invariant test: apps are told apart by glyph, so a new app may not reuse one (track-bars is the pre-existing exception)"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "PATH=~/sdk/fvm/versions/3.47.7/bin:$PATH python3 -m python_pkg.app_icons generate --app daily_limits --android",
|
||||
"result": "pass",
|
||||
"evidence": "daily_limits: wrote 6 asset files; regenerated Android mipmaps; ic_launcher_background #211D1B"
|
||||
},
|
||||
{
|
||||
"command": "phone_deploy.sh ~/src/daily-limits/app (adb install -r on 23181JEGR08034)",
|
||||
"result": "pass",
|
||||
"evidence": "Install Success; the APK's mipmap-xxxhdpi ic_launcher.png is byte-identical to the generated one (hourglass in #B8862E on #211D1B); confirmed by kuhy"
|
||||
},
|
||||
{
|
||||
"command": ".venv/bin/python -m pytest -q python_pkg/app_icons --cov=python_pkg/app_icons --cov-branch",
|
||||
"result": "pass",
|
||||
"evidence": "47 passed; every python_pkg/app_icons module 100% line and branch"
|
||||
},
|
||||
{
|
||||
"command": "git diff python_pkg/app_icons | grep -c '^-[^-]'",
|
||||
"result": "pass",
|
||||
"evidence": "0 removed lines: renderer and other registry entries untouched, so other apps' icons are byte-identical"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"`generate` runs `dart run`, which needs a Dart SDK satisfying the app's ^3.13.5; the system dart 3.13.3 fails, so put the fvm 3.47.7 SDK first on PATH"
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit; daily_limits disappears from the registry and the glyph library"
|
||||
]
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"intent": "Give a freshly installed machine a working escape hatch from the hosts file-guards. Until now --rollback refused to run on exactly the machines the previous commit created.",
|
||||
"scope": [
|
||||
"linux_configuration/fixes/lib/hosts_guard_rollback.sh only.",
|
||||
"Follow-up to 4c426eae, which made hosts-blocker's install.sh register the guard-lib instances directly.",
|
||||
"Non-goal: changing what the migration path records; a migrated machine keeps its existing legacy backup and restore behaviour unchanged."
|
||||
],
|
||||
"changes": [
|
||||
"do_rollback no longer exits when /var/lib/guard-lib-migration is absent. That state dir is written by the one-shot migration, but a machine set up by hosts-blocker's install.sh has no legacy layer to back up and therefore no state dir -- so --rollback bailed out while the very instances it exists to remove were live. It now proceeds whenever a guard-lib instance is registered, warns that there is no legacy layer to restore, and still refuses when there is genuinely nothing to roll back.",
|
||||
"After uninstalling the instances, re-assert chattr +i on /etc/hosts when no legacy unit did it. The existing re-assertion relied on starting hosts-guard.service / hosts-bind-mount.service, which do not exist on a fresh machine, so every systemctl cat skipped and the rollback left /etc/hosts with no immutable flag and no bind mount -- strictly less protected than before the rollback."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "sudo bash linux_configuration/fixes/migrate_hosts_guard_to_guard_lib.sh --rollback --dry-run (pristine vmbox guest, guards installed by install_core_system.sh --all)",
|
||||
"result": "pass",
|
||||
"evidence": "DRYRUN_RC=0, reporting it would uninstall the hosts, nsswitch and resolved instances. Before this change the same command printed 'no rollback state at /var/lib/guard-lib-migration - nothing to roll back to' and exited 1."
|
||||
},
|
||||
{
|
||||
"command": "sudo bash linux_configuration/fixes/migrate_hosts_guard_to_guard_lib.sh --rollback (same guest)",
|
||||
"result": "pass",
|
||||
"evidence": "ROLLBACK_RC=0. Uninstalled file-guard hosts, nsswitch and resolved, then logged 're-asserted chattr +i on /etc/hosts (no legacy unit to do it)'."
|
||||
},
|
||||
{
|
||||
"command": "ls /etc/guard-lib/targets; lsattr /etc/hosts; findmnt /etc/hosts; wc -l /etc/hosts (after rollback)",
|
||||
"result": "pass",
|
||||
"evidence": "Only shutdown-schedule.conf remains, which belongs to a different feature and must not be touched. /etc/hosts shows ----i----------------- and still holds all 178410 lines; the bind mount is gone, which is the intended post-rollback state."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck -x linux_configuration/fixes/lib/hosts_guard_rollback.sh",
|
||||
"result": "pass",
|
||||
"evidence": "Clean. An initial A && B || C form was rewritten as if/else after SC2015 pointed out the C branch can run when A succeeds, which matters because `run` is a no-op under --dry-run."
|
||||
},
|
||||
{
|
||||
"command": "bash ./linux_configuration/fixes/lib/tests/run_all.sh",
|
||||
"result": "pass",
|
||||
"evidence": "hosts_guard_rollback: 31 passed, 0 failed; whole suite exits 0. The first attempt failed one case: the refusal message is asserted verbatim by test_hosts_guard_rollback.sh:115, and the new no-instance branch had dropped the trailing 'to'. The wording was restored rather than the assertion loosened, since the test is checking the user-visible explanation."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Rollback is now reachable in more situations, so an accidental invocation can unregister live guards. It still refuses when no instance is registered, and it keeps the canonical copy via --keep-canonical, so the file itself is never lost.",
|
||||
"The re-assertion only covers /etc/hosts. nsswitch.conf and resolved.conf are left to their plugins, matching the behaviour the migration path already had."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit; the previous behaviour (refuse without a state dir) returns.",
|
||||
"After reverting, confirm with sudo bash linux_configuration/fixes/migrate_hosts_guard_to_guard_lib.sh --status that the instance list matches expectations before relying on it."
|
||||
]
|
||||
}
|
||||
@ -207,3 +207,25 @@ _OPEN_BOOK = """\
|
||||
<path fill="{{ACCENT}}" stroke="none" d="\
|
||||
M 232 344 Q 372 290 488 360 L 488 744 Q 372 674 232 728 Z \
|
||||
M 536 360 Q 652 290 792 344 L 792 728 Q 652 674 536 744 Z"/>"""
|
||||
|
||||
|
||||
# An hourglass with sand still in the top bulb -- daily-limits, today's PC
|
||||
# shutdown time and gaming budget: a daily allowance that runs out. Chosen over
|
||||
# a clock-with-a-cap because `clock` is already wake_alarm_sync's glyph.
|
||||
#
|
||||
# Geometry, inside SAFE_BOX (ink x=284..740, y=236..788):
|
||||
# caps bars at y=272 and y=752, x=320..704.
|
||||
# glass straight sides (352,272)->(512,512)->(352,752) and mirror; the
|
||||
# sides cross at the neck, so the waist is solid, not a sliver.
|
||||
# sand filled to the glass centrelines, so it fuses with the outline:
|
||||
# top bulb from the neck up to y=392, leaving an empty band 84 tall
|
||||
# and 73+ wide below the cap; bottom bulb a pile up to y=680. Every
|
||||
# gap clears MIN_NEGATIVE_SPACE=36.
|
||||
_HOURGLASS = """\
|
||||
<path d="M 320 272 L 704 272"/>
|
||||
<path d="M 320 752 L 704 752"/>
|
||||
<path d="M 352 272 L 672 752"/>
|
||||
<path d="M 672 272 L 352 752"/>
|
||||
<path fill="{{ACCENT}}" stroke="none" d="\
|
||||
M 432 392 L 592 392 L 512 512 Z \
|
||||
M 400 680 L 624 680 L 672 752 L 352 752 Z"/>"""
|
||||
|
||||
@ -211,6 +211,14 @@ APPS: Final[dict[str, AppIcon]] = {
|
||||
icon_name="book-guard",
|
||||
linux=False,
|
||||
),
|
||||
AppIcon(
|
||||
key="daily_limits",
|
||||
repo=_HOME / "src/daily-limits/app",
|
||||
accent="#B8862E",
|
||||
glyph="hourglass",
|
||||
icon_name="daily-limits",
|
||||
linux=False,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ from python_pkg.app_icons._glyph_art import (
|
||||
)
|
||||
from python_pkg.app_icons._glyph_art_more import (
|
||||
_BOWL_STAR,
|
||||
_HOURGLASS,
|
||||
_HOUSE_TICK,
|
||||
_NOTE_ASCENDER,
|
||||
_OPEN_BOOK,
|
||||
@ -94,6 +95,7 @@ GLYPHS: Final[dict[str, Glyph]] = {
|
||||
Glyph("house-tick", "House outline with a tick inside", _HOUSE_TICK),
|
||||
Glyph("umbrella", "Filled umbrella canopy over a J-handle", _UMBRELLA),
|
||||
Glyph("open-book", "Open book, two filled pages", _OPEN_BOOK),
|
||||
Glyph("hourglass", "Capped hourglass with sand still in the top", _HOURGLASS),
|
||||
Glyph(
|
||||
"remote-wave",
|
||||
"Handset with punched buttons under a broadcast arc",
|
||||
|
||||
@ -45,6 +45,13 @@ class TestFamilyInvariants:
|
||||
assert len(app.accent) == 7
|
||||
int(app.accent[1:], 16)
|
||||
|
||||
def test_no_glyph_is_reused(self) -> None:
|
||||
"""Apps are told apart by glyph; a new app must not borrow one."""
|
||||
shared_by_design = {"track-bars"} # kuhylog + betting_sim, pre-existing
|
||||
used = [app.glyph for app in apps.APPS.values()]
|
||||
reused = {glyph for glyph in used if used.count(glyph) > 1}
|
||||
assert reused <= shared_by_design
|
||||
|
||||
def test_safe_box_fits_inside_canvas(self) -> None:
|
||||
assert style.SAFE_BOX < style.CANVAS
|
||||
assert style.MIN_NEGATIVE_SPACE == style.STROKE_WIDTH / 2
|
||||
@ -64,3 +71,14 @@ class TestFamilyInvariants:
|
||||
f"{glyph.name} paints style.BACKGROUND; cut the hole with "
|
||||
f'fill-rule="evenodd" instead'
|
||||
)
|
||||
|
||||
|
||||
class TestHourglassGlyph:
|
||||
def test_daily_limits_uses_it(self) -> None:
|
||||
assert apps.get_app("daily_limits").glyph == "hourglass"
|
||||
|
||||
def test_sand_is_filled_in_the_accent(self) -> None:
|
||||
"""The sand takes the accent marker, so the monochrome layer recolours it."""
|
||||
body = glyphs.get_glyph("hourglass").body
|
||||
assert f'fill="{style.ACCENT_MARKER}"' in body
|
||||
assert 'stroke="none"' in body
|
||||
|
||||
Loading…
Reference in New Issue
Block a user