🔍 Problem
The 5h/7d quota animation state is 8 parallel scalar fields (quotaSpring/Ratio/Vel/Target × {5h, 7d}, pkg/tui/model.go:327-334) manipulated by mirrored two-block copy-paste at every site:
- 🪦
springs.go:124-134 (advanceSpringGrowing) — the 7d block at 130-134 is a character-level copy of the 5h block
- 🪦
springs.go:171-184 (kickLateArrivalQuotaIntro)
- 🪦
springs.go:450-467 (beginIntroAnimation)
- 🪦
springs.go:217-233 (quotaIntroRatio's quotaSide dispatch switch)
- 🪦 seeding pair at
springs.go:99-100
The quotaSide enum (model.go:121-126) exists solely to pick which 4-tuple of fields to read. Every quota-animation change is written twice — a classic missed-edit hazard.
🛠️ Suggested shape
A scalarSpring struct { spring harmonica.Spring; ratio, vel, target float64 } with arm(target) / step() (gap float64) methods, and quota [2]scalarSpring indexed by quotaSide — collapses all four duplicated blocks to loops and deletes quotaIntroRatio's inner switch.
⚠️ Notes
🔍 Problem
The 5h/7d quota animation state is 8 parallel scalar fields (
quotaSpring/Ratio/Vel/Target× {5h, 7d},pkg/tui/model.go:327-334) manipulated by mirrored two-block copy-paste at every site:springs.go:124-134(advanceSpringGrowing) — the 7d block at 130-134 is a character-level copy of the 5h blocksprings.go:171-184(kickLateArrivalQuotaIntro)springs.go:450-467(beginIntroAnimation)springs.go:217-233(quotaIntroRatio'squotaSidedispatch switch)springs.go:99-100The
quotaSideenum (model.go:121-126) exists solely to pick which 4-tuple of fields to read. Every quota-animation change is written twice — a classic missed-edit hazard.🛠️ Suggested shape
A
scalarSpring struct { spring harmonica.Spring; ratio, vel, target float64 }witharm(target)/step() (gap float64)methods, andquota [2]scalarSpringindexed byquotaSide— collapses all four duplicated blocks to loops and deletesquotaIntroRatio's inner switch.model_test.gopokes these fields directly; the rename is mechanical but wide.springTickMsg, never a real now-tick Cmd.