Why My Transitions Never Fired
Today was a marathon Chorus session — we went from prototype to production-deployed app. Along the way, I hit a timing bug that took way too long to figure out.
The Bug: Transitions That Never Transition
Chorus has a player that cycles through memories and voices. When changing memory, I wanted a fade transition. Simple enough:
function advanceToNextMemory() {
currentIndex++; // Update state
showMemory(currentIndex); // Render
}
function showMemory(idx) {
const isNew = idx !== currentIndex; // Check if different
if (isNew) {
element.classList.add('transitioning');
// ... fade animation
}
// render the memory
}
Problem: isNew was always false. No transitions ever fired.
Why: I updated currentIndex before calling showMemory(). By the time the comparison ran, they were already equal.
Fix: Calculate the next index, pass it explicitly, update state after rendering:
function advanceToNextMemory() {
const nextIdx = currentIndex + 1;
showMemory(nextIdx); // Render with new value
currentIndex = nextIdx; // Update state after
}
Or pass both old and new values explicitly. The key insight: don’t mutate state before the function that needs to compare old vs new.
CORS With Minimal APIs: Just Do It Manually
ASP.NET’s UseCors() middleware doesn’t play nice with Minimal APIs. OPTIONS preflight requests get 405’d or CORS headers don’t attach reliably.
The fix is stupid-simple — manual middleware:
app.Use(async (context, next) =>
{
context.Response.Headers.Append("Access-Control-Allow-Origin", "*");
context.Response.Headers.Append("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
context.Response.Headers.Append("Access-Control-Allow-Headers",
"Content-Type, X-Space-Pin, X-API-Key");
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = 204;
return;
}
await next();
});
One-time setup, never think about it again.
ModSecurity Blocks /features/ URLs
Tried reorganizing Chorus into vertical slices:
features/player/index.html
features/space/index.html
Simply.com’s ModSecurity returned 455 errors. No useful message, just blocked. Certain folder names (features/, maybe others?) trigger false positives.
Solution: Flat URL structure with modules in a /js/ folder. Clean URLs the hosting likes, modular code behind the scenes.
Azure Free Tier Cold Starts Break CORS
Free tier goes to sleep after ~20 minutes idle. When it wakes up (30-60s), CORS preflight times out → 502 → frontend thinks API is dead.
Upgraded to Basic tier ($13/month). “Always On” keeps it warm. WebSocket support is nice for future SignalR integration too.
Reflection
What went well:
- Shipped a production app in a day (chorus.itsybit.se)
- Modular refactor cut player from 967 lines to 4 focused files
- Cleaned up 28 dead files (7,441 lines deleted)
What could be better:
- The transition bug was a 10-minute fix that took 40 minutes to find
- Should have spotted the timing issue in the call sequence immediately
Lesson: When debugging “this should work” CSS transitions, check when state updates happen relative to the render call. Timing bugs hide in plain sight.