testsAndMisc/python_pkg/app_icons/_glyph_art_more.py
Krzysztof kuhy Rudnicki 1d7271f023 Phase 2: enforce the 250-line cap and stop pushing red
Two pre-commit hooks and one pre-push extension, plus the _glyph_art split
that had to land first — the cap gate cannot be wired while any file is over
it, and the lyricanki note-ascender glyph had taken _glyph_art.py to 253.

file-length-cap runs check_file_length.sh --all repo-wide rather than on
staged files, so a file that grows past the cap in some other commit is still
caught. Proven to block, not assumed: with a deliberate 261-line file staged
the hook exits 1 and names it; with the file removed it passes.

ci-baseline-green is the user's option (a): refuse to build on a main that is
already red. It fails closed on a definitively failing baseline and warns-and-
passes on anything it cannot determine — offline, no gh, unauthenticated, no
settled runs — each call capped at 5s. Blocking every commit on network
reachability buys nothing when (b) is the real per-change guarantee.
CI_GREEN_SKIP=1 is the escape for a fix-forward commit. python-tests.yml is
excluded on purpose: it is path-filtered, so a shell-only commit never
triggers it and its "latest run" would produce false reds.

This hook already earned its place. Run against a red main earlier in this
session it refused with "Shell tests (failure)", which is how the broken
SETUP_SOURCES was found instead of being pushed over.

ci_mirror gains the shell-tests workflow's side-effect-free half — every
lib/tests/run_all.sh plus the workflow's own test list, the user's option (b).
That list is parsed out of shell-tests.yml rather than copied: a hardcoded
duplicate drifts the moment someone adds a test, and a mirror that quietly
checks less than CI is worse than no mirror because it reports safe. The
Arch-container half stays unmirrored — it wants root and an Arch userland.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:41:31 +02:00

51 lines
2.6 KiB
Python

"""More SVG path fragments, continued from :mod:`_glyph_art`.
Split off purely to keep both modules under the 250-line cap; the division
between the two files carries no meaning beyond that. :mod:`glyphs` imports
from both, so no caller or test needs to know which file a glyph lives in.
"""
from __future__ import annotations
# Three rising bars with a plotted point above the tallest -- a day-by-day log
# that adds up to a trend, which is what a life tracker is. The bars are
# strokes rather than filled rects so the glyph keeps one stroke weight
# throughout; at a 168px pitch the 72px strokes leave 96px gaps, well above
# MIN_NEGATIVE_SPACE. The marker sits on its own row clear of the bar caps,
# and is hollow so it reads as a data point rather than a full stop.
_TRACK_BARS = """\
<path d="M 320 700 L 320 556"/>
<path d="M 488 700 L 488 460"/>
<path d="M 656 700 L 656 364"/>
<circle cx="656" cy="252" r="40" fill="none"/>"""
# A note whose stem drops past the head onto a flat baseline foot, so one
# vertical stroke reads as both a music stem and a letterform ascender sitting
# on a text line -- lyricanki turns songs into words. Filled, not stroked, for
# the nib/anvil reason: at 72px weight an outlined note-head's interior is
# under MIN_NEGATIVE_SPACE and fills in solid at 48dp.
#
# Geometry, all inside SAFE_BOX (x=232..792, y=232..792):
# stem x=520..592, y=272..584 (72 wide, the family stroke weight)
# head ellipse (501,580) rx=118 ry=90 rot -20deg: y=486..674, x=386..616,
# overlapping the stem's end, so the stem stops at y=584
# flags two stacked wedges off the stem top, reaching x=756 and x=736
# foot x=232..792, y=724..776 (52 tall, reads as the baseline)
#
# The head's lowest point is y=674: a rotated ellipse reaches
# sqrt((rx*sin t)^2 + (ry*cos t)^2) below its centre, NOT ry. That leaves 50px
# above the foot, clear of MIN_NEGATIVE_SPACE=36. A first draft used ry=104 at
# cy=672 and recorded "40px clearance" without doing that sum -- the head
# overlapped the foot by 40px and rendered as a mailbox. Recompute, never eyeball.
#
# Rejected: a bare note says nothing about words; a speech bubble around one
# has nested outlines that vanish at 48dp.
_NOTE_ASCENDER = """\
<path fill="{{ACCENT}}" fill-rule="evenodd" stroke="none" d="\
M 520 272 L 592 272 L 592 584 L 520 584 Z \
M 592 272 Q 706 332 756 416 Q 760 316 592 220 Z \
M 592 352 Q 694 408 736 480 Q 740 392 592 302 Z \
M 232 724 L 792 724 L 792 776 L 232 776 Z"/>
<ellipse fill="{{ACCENT}}" stroke="none" \
cx="501" cy="580" rx="118" ry="90" transform="rotate(-20 501 580)"/>"""