Files
community-plugins/claude-companion/barpulse.luau
T
Jarred RobinsonandGitHub 14215c8cd4 feat: add claude-companion (#33)
* Add lowcache/claude-companion plugin for PR

* claude-companion: address review feedback

...

* Added tr & timeout to dependency list, and reduced thumbnail description

* added missing thumbnail.webp
2026-07-19 08:46:30 -04:00

90 lines
3.9 KiB
Luau

-- EXPERIMENTAL — prototyping always-visible bar-widget animation, since the desktop
-- orb only shows on bare desktop. The real pulse.luau bar is UNTOUCHED; this is a
-- throwaway comparison surface. The winning behaviour gets folded into pulse.luau.
--
-- Bar plugin widgets have NO per-frame tick (that's desktop-only) — they re-render on
-- a timer via noctalia.setUpdateInterval(ms) + a global update(). The only animatable
-- channel is the glyph colour. Rather than alpha (the bar didn't honour 8-digit
-- #RRGGBBAA), we breathe BRIGHTNESS: scale the accent RGB toward black (the near-black
-- surface) and emit a plain 6-digit "#RRGGBB" so the glyph glows brighter/dimmer while
-- keeping its hue. Two waveforms to compare by eye:
-- MODE "A" raised-cosine sine breath (smooth glow, like the orb)
-- MODE "B" square wave (discrete blink)
--
-- It reads state from the same claude.pulse rollup pulse.luau publishes (so it needs none
-- of the session bookkeeping). Flip MODE + reload to compare.
--
-- State RGB is hardcoded from the active dark palette (volnix:
-- mSecondary/mPrimary/mError) to validate the look fast.
local MODE = "A" -- "A" sine breath | "B" square blink
local INTERVAL_MS = 60 -- ~16 fps re-render (bars can't do 60 fps)
local BMIN, BMAX = 0.45, 1.00 -- brightness floor/ceiling the glyph breathes between
-- glyph + accent RGB (hex, no #) per state — same glyph/role map as the bar dot/orb.
local VISUAL = {
idle = { glyph = "robot", rgb = "EAFF00", period = 8.0 },
turn_start = { glyph = "brain", rgb = "B4FF00", period = 5.0 },
text = { glyph = "message-dots", rgb = "B4FF00", period = 4.5 },
tool_start = { glyph = "tool", rgb = "EAFF00", period = 5.0 },
needs_attention = { glyph = "bell-ringing", rgb = "FF4D1F", period = 3.0 },
turn_end = { glyph = "bell", rgb = "B4FF00", period = 5.5 },
error = { glyph = "alert-triangle", rgb = "FF4D1F", period = 3.5 },
}
local state = "idle"
local phase = 0.0
local function level_for(v) -- brightness scale in [BMIN, BMAX]
local t = phase % v.period
if MODE == "B" then
return (t < v.period * 0.5) and BMAX or BMIN -- bright for the first half, dim for the second
end
local s = 0.5 - 0.5 * math.cos((t / v.period) * 2 * math.pi)
return BMIN + (BMAX - BMIN) * s
end
-- Scale an "RRGGBB" accent toward black by factor b, returning "#RRGGBB".
local function dim(rgb, b)
local function ch(i)
local x = math.floor((tonumber(rgb:sub(i, i + 1), 16) or 0) * b + 0.5)
if x < 0 then x = 0 elseif x > 255 then x = 255 end
return x
end
return string.format("#%02X%02X%02X", ch(1), ch(3), ch(5))
end
local function paint()
local v = VISUAL[state] or VISUAL.idle
barWidget.setGlyph(v.glyph)
barWidget.setGlyphColor(dim(v.rgb, level_for(v)))
barWidget.setTooltip(string.format("barpulse [%s] · %s", MODE, state))
end
-- Pull the most-urgent state from the bar pulse's published rollup. Bar widgets don't
-- seem to receive noctalia.state.watch callbacks (the desktop orb does; this bar one
-- did not), so we POLL noctalia.state.get each tick instead — cheap, and the timer is
-- already running. Only adopt a recognised state so a nil/garbage read can't blank it.
local function refresh_state()
local snap = noctalia.state.get and noctalia.state.get("claude.pulse")
if type(snap) == "table" and type(snap.state) == "string" and VISUAL[snap.state] then
state = snap.state
elseif type(snap) == "string" and VISUAL[snap] then
state = snap
end
end
-- Timer loop. Re-arm the interval each tick (mirrors the scratchpad pattern), refresh
-- the state by polling, and advance the breath by the known step (no dt on bar widgets).
function update()
noctalia.setUpdateInterval(INTERVAL_MS)
refresh_state()
phase = phase + INTERVAL_MS / 1000
if phase > 1e6 then phase = 0 end
paint()
end
refresh_state()
noctalia.setUpdateInterval(INTERVAL_MS)
paint()