Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions src/prism/workshop/bands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package workshop

import (
"fmt"
"math"
"strings"

"charm.land/lipgloss/v2"
"github.com/snivilised/jaywalk/src/agenor/enums"
"github.com/snivilised/jaywalk/src/prism/contract"
)

// brailleHeights maps a normalised [0,1] value to a
// braille character whose dot pattern corresponds to
// the vertical position (bottom to top).
var brailleHeights = []string{
"⣀", // 0.0 bottom
"⡠", // 0.25
"⠤", // 0.5 middle
"⠊", // 0.75
"⠈", // 1.0 top
}

// BandsVisualiser renders a flat gradient bar with a
// braille curve indicator row above it showing the
// easing curve shape.
type BandsVisualiser struct {
Width int
}
Comment on lines +27 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how/if Width gets set on visualisers obtained via the registry.
fd . -e go --full-path 'src/prism/workshop' -x cat -n {} \; 2>/dev/null | grep -n "func Register\|func Visualisers\|\.Width" 

ast-grep run --pattern 'func Register($_) $_' --lang go src/prism/workshop
ast-grep run --pattern 'func Visualisers($$$) $_' --lang go src/prism/workshop
rg -n -B2 -A15 'func RegisterVisualisers' src/prism/workshop/visualiser.go
rg -nP '\.Width\s*=' src/prism/workshop

Repository: snivilised/jaywalk

Length of output: 1790


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any direct mutations of Width on BandsVisualiser or in the workshop package.
rg -n --hidden --glob '!**/vendor/**' --glob '!**/.git/**' \
  'BandsVisualiser|\.Width\s*=|Width:\s*' .

# Inspect the workshop registry and render call sites for any width-setting path.
rg -n --hidden --glob '!**/vendor/**' --glob '!**/.git/**' \
  'Visualisers\(\)|Render\(' src/prism/workshop

# Show the relevant workshop files around registry/render definitions.
sed -n '1,140p' src/prism/workshop/visualiser.go
sed -n '1,120p' src/prism/workshop/bands.go

Repository: snivilised/jaywalk

Length of output: 15709


BandsVisualiser is registered with Width left at zero, so the registry path renders empty output. RegisterVisualisers() adds &BandsVisualiser{} and nothing in GradientVisualiser or visualiser.go sets Width, so the registered instance always hits v.Width <= 0 and returns "".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prism/workshop/bands.go` around lines 27 - 29, The registered
BandsVisualiser is being created with Width left unset, which causes Render to
return empty output when it checks v.Width <= 0. Update RegisterVisualisers() to
initialize BandsVisualiser with a valid Width, or set a default Width inside
BandsVisualiser/its render path so the registry-created instance can render
correctly. Use the BandsVisualiser type, its Render method, and
RegisterVisualisers() in visualiser.go to locate the fix.


// Name returns the display name for this visualiser.
func (v *BandsVisualiser) Name() string {
return "bands"
}

// Render produces two rows of output:
// - Row 1: braille curve indicator tracing the easing
// curve shape.
// - Row 2: flat gradient bar with evenly distributed
// gradient steps.
func (v *BandsVisualiser) Render(
steps []contract.Color,
curve enums.CurveKind,
easing enums.EasingKind,
animFrame int,
) string {
if len(steps) == 0 || v.Width <= 0 {
return ""
}

var b strings.Builder
n := len(steps)

// Row 1: braille curve indicator
mutedColor := "#888888"
mutedStyle := lipgloss.NewStyle().Foreground(
lipgloss.Color(mutedColor),
)

for col := 0; col < v.Width; col++ {
t := float64(col) / float64(v.Width)
y := easedCurve(t, curve, easing)
ch := brailleForHeight(y)
b.WriteString(mutedStyle.Render(ch))
}

b.WriteString("\n")

// Row 2: gradient bar
for col := 0; col < v.Width; col++ {
t := float64(col) / float64(v.Width)
stepIdx := stepForPosition(t, n, curve)

c := steps[stepIdx]
style := lipgloss.NewStyle().Foreground(
lipgloss.Color(
fmt.Sprintf(
"#%02x%02x%02x", c.R, c.G, c.B,
),
),
)
b.WriteString(style.Render("█"))
}

return b.String()
}

// brailleForHeight maps a value in [0,1] to a braille
// character. 0 maps to the bottom row, 1 maps to the
// top row.
func brailleForHeight(y float64) string {
n := len(brailleHeights)
if y <= 0 {
return brailleHeights[0]
}
if y >= 1 {
return brailleHeights[n-1]
}
idx := int(math.Round(y * float64(n-1)))
if idx < 0 {
idx = 0
}
if idx >= n {
idx = n - 1
}
return brailleHeights[idx]
}

// easedCurve applies the curve shaping then easing
// distribution to a linear parameter t in [0,1].
func easedCurve(
t float64,
curve enums.CurveKind,
easing enums.EasingKind,
) float64 {
t = applyCurve(t, curve)
t = applyEasing(t, easing)
return t
}

// applyCurve applies the curve shaping function to a
// linear parameter t in [0,1].
func applyCurve(t float64, curve enums.CurveKind) float64 {
switch curve { //nolint:exhaustive // ok
case enums.CurveKindSine:
return (1 - math.Cos(math.Pi*t)) / 2
case enums.CurveKindQuadraticIn:
return t * t
case enums.CurveKindQuadraticOut:
return 1 - (1-t)*(1-t)
case enums.CurveKindCubic:
return 3*t*t - 2*t*t*t
default:
return t
}
}

// applyEasing applies the easing distribution function
// to a parameter t in [0,1].
func applyEasing(t float64, easing enums.EasingKind) float64 {
switch easing { //nolint:exhaustive // ok
case enums.EasingKindEaseIn:
return t * t
case enums.EasingKindEaseOut:
return 1 - (1-t)*(1-t)
case enums.EasingKindEaseInOut:
return (1 - math.Cos(math.Pi*t)) / 2
default:
return t
}
}
81 changes: 81 additions & 0 deletions src/prism/workshop/bands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package workshop_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/snivilised/jaywalk/src/agenor/enums"
"github.com/snivilised/jaywalk/src/prism/workshop"
)

var _ = Describe("BandsVisualiser", func() {
var v *workshop.BandsVisualiser

BeforeEach(func() {
v = &workshop.BandsVisualiser{Width: 40}
})

It("returns the name 'bands'", func() {
Expect(v.Name()).To(Equal("bands"))
})

DescribeTable("renders gradient steps",
func(entry stepsEntry) {
result := v.Render(
makeSteps(entry.Count),
entry.Curve,
entry.Easing,
entry.AnimFrame,
)
Expect(result).ToNot(BeEmpty())
Expect(result).To(ContainSubstring("\x1b[38;2;"))
Expect(result).To(ContainSubstring("\n"))
},
Entry("2 steps, linear, uniform, frame 0",
stepsEntry{2, enums.CurveKindLinear,
enums.EasingKindUniform, 0}),
Entry("8 steps, sine, ease-in, frame 10",
stepsEntry{8, enums.CurveKindSine,
enums.EasingKindEaseIn, 10}),
Entry("64 steps, cubic, ease-out, frame 100",
stepsEntry{64, enums.CurveKindCubic,
enums.EasingKindEaseOut, 100}),
Entry("256 steps, q-in, ease-in-out, frame 0",
stepsEntry{256, enums.CurveKindQuadraticIn,
enums.EasingKindEaseInOut, 0}),
Entry("8 steps, q-out, uniform, frame 50",
stepsEntry{8, enums.CurveKindQuadraticOut,
enums.EasingKindUniform, 50}),
)

DescribeTable("contains braille and block chars",
func(entry stepsEntry) {
result := v.Render(
makeSteps(entry.Count),
entry.Curve,
entry.Easing,
entry.AnimFrame,
)

Expect(result).To(Satisfy(
func(s string) bool {
return containsAny(s, []string{
"⣀", "⡠", "⠤", "⠊", "⠈", "█",
})
},
))
},
Entry("8 steps, linear",
stepsEntry{8, enums.CurveKindLinear,
enums.EasingKindUniform, 0}),
)

It("returns empty for zero steps", func() {
result := v.Render(
makeSteps(0),
enums.CurveKindLinear,
enums.EasingKindUniform,
0,
)
Expect(result).To(BeEmpty())
})
})
Loading
Loading