feat(P2): Stash dashboard — Bread scale + maturity progress (REQ-041)

web/handlers/stash.go: GET /stash/{holderID} renders balance in Grain +
Bread-scale conversion (x/bread/types.BreadScaleAll() + GrainsPerBread=10000,
D-073 code constants NOT docs) + 90-day maturity progress bar (ActiveDays/90
capped at 100%) + IsMature badge. stash_test.go: D-073 regression guard
(GrainsPerBread=10000, Crumb=100 Grain; would fail if docs 1000x values used),
mature vs immature fixture, 404 + G-026 error lexicon check. Coverage 83.5%
cumulative. Template FuncMap divGrain for the scale table.

---ci---
project: oy
phase: 2
milestone: v0.6
status: execute
---/ci---
This commit is contained in:
2026-08-18 18:55:03 +00:00
parent 741d6e0a96
commit 973e25a7c9
4 changed files with 291 additions and 2 deletions
+11 -2
View File
@@ -33,8 +33,16 @@ type Server struct {
// New constructs a Server with the given store + per-page templates loaded
// from templatesDir (the absolute or relative path to web/templates/).
func New(s *store.Store, templatesDir string) (*Server, error) {
funcs := template.FuncMap{
"divGrain": func(grain, unit int64) int64 {
if unit == 0 {
return 0
}
return grain / unit
},
}
basePath := filepath.Join(templatesDir, "base.html")
base, err := template.ParseFiles(basePath)
base, err := template.New("base.html").Funcs(funcs).ParseFiles(basePath)
if err != nil {
return nil, fmt.Errorf("parse base: %w", err)
}
@@ -66,7 +74,8 @@ func New(s *store.Store, templatesDir string) (*Server, error) {
// patterns). Called by web/server.go after constructing the Server.
func (s *Server) Register(mux *http.ServeMux) {
s.registerReach(mux)
// P2..P5 register their own routes (stash, window, standing, bloom).
s.registerStash(mux)
// P3..P5 register their own routes (window, standing, bloom).
}
// render executes the named page template with the given data, writing HTML