Skip to content

Build customizable cooking timer for any recipe#184

Merged
simonw merged 1 commit intomainfrom
claude/cooking-timer-customizable-B69J9
Dec 27, 2025
Merged

Build customizable cooking timer for any recipe#184
simonw merged 1 commit intomainfrom
claude/cooking-timer-customizable-B69J9

Conversation

@simonw
Copy link
Owner

@simonw simonw commented Dec 27, 2025

Build cooking-timer.html - start with a copy of the existing timer that talks about chickpea stew, create a modified version that can support any recipe. When you first start it running it shows a starter field where it asks you to paste in either the URL to a recipe.json or the recipe JSON directly - it includes an example of the recipe format (make up a fun recipe for guacamole for the example) as JSON which you can copy and paste to somewhere else, plus a link that says "Create one with Claude" which links to the claude.ai page with a querystring parameter that pre-populates Claude and populates it with an example JSON document and a following prompt that says "output this format for the following recipe:"

If you enter a URL the page redirects to ?url=THAT_URL - any time you load the page with that format it attempts to fetch() the JSON from that URL and use it

If you paste in JSON instead then it stashes that in localStorage. It can actually stash multiple recipes in localStorage and shows a list on that initial homepage of stored recipes (with their names and creation dates - make sure the JSON format has a space for a name) for the user to select

For any recipe you have loaded by URL that URL and the recipe name are also stored in localstorage so the "previous recipes" UI can link to those as well

Once you have selected a recipe the tool works exactly the same way as the original. It uses localStorage to remember the time such that if you refresh the page it can jump right back to where you are

Add a reset button with. a clear name that goes back to that initial state.

Create cooking-timer.html that allows users to load recipes via:

  • URL parameter (?url=...) for recipe.json files
  • Pasting JSON directly into the interface
  • Selecting from previously saved recipes in localStorage

Features:

  • Example guacamole recipe with copyable JSON format
  • Link to Claude for generating new recipe timers
  • Persistent timer state across page refreshes
  • Recipe history with delete functionality
  • "Choose Different Recipe" button to return to selection screen

https://gistpreview.github.io/?33f973d84f202869a4567e6af097315e/index.html

Create cooking-timer.html that allows users to load recipes via:
- URL parameter (?url=...) for recipe.json files
- Pasting JSON directly into the interface
- Selecting from previously saved recipes in localStorage

Features:
- Example guacamole recipe with copyable JSON format
- Link to Claude for generating new recipe timers
- Persistent timer state across page refreshes
- Recipe history with delete functionality
- "Choose Different Recipe" button to return to selection screen
@claude
Copy link
Contributor

claude bot commented Dec 27, 2025

Claude finished @simonw's task —— View job


PR Review: Customizable Cooking Timer

I've completed my review of the cooking timer implementation. Here's my detailed feedback:

Todo List:

  • Read and analyze the cooking-timer.html file
  • Review code quality and JavaScript best practices
  • Check for potential bugs and edge cases
  • Assess performance considerations
  • Identify security concerns
  • Evaluate overall implementation approach
  • Provide constructive feedback and recommendations

Review Summary

This is a well-crafted, feature-complete cooking timer that successfully implements all the requested functionality. The code quality is high with good separation of concerns and user experience considerations.

Strengths

Code Quality

  • Clean structure: Well-organized HTML, CSS, and JavaScript with logical separation
  • Responsive design: Mobile-friendly interface with proper viewport settings
  • Good naming conventions: Clear, descriptive function and variable names
  • Error handling: Comprehensive try-catch blocks and user-friendly error messages
  • State persistence: Robust localStorage implementation with proper serialization

User Experience

  • Intuitive interface: Clear visual hierarchy and smooth transitions
  • Persistent state: Timer survives page refreshes seamlessly
  • Recipe history: Easy access to previously used recipes with delete functionality
  • Visual feedback: Real-time updates, progress indicators, and status animations

Security

  • XSS prevention: Proper HTML escaping via escapeHtml() function on user content
  • URL validation: Basic URL format validation before fetching
  • Safe JSON parsing: Wrapped in try-catch blocks

⚠️ Areas for Improvement

Security Considerations

  1. CORS/Network vulnerabilities (cooking-timer.html:834):
    • No timeout on fetch requests - could hang indefinitely
    • Missing CORS error handling for cross-origin recipe URLs
    • No validation of response content-type (could process non-JSON responses)

Error Handling & Edge Cases

  1. Recipe validation (cooking-timer.html:884-895):

    • Validation doesn't check if steps are properly sorted by time
    • No validation for negative time values
    • Missing checks for duplicate time values
  2. Timer logic (cooking-timer.html:1024-1046):

    • Complex current step logic could miss edge cases
    • No handling for steps that occur at exactly the same time
    • Timer continues running even when page is inactive (could cause battery drain)

Performance

  1. Memory management:
    • Timer interval not cleared on page unload
    • No cleanup of event listeners
    • LocalStorage could grow large with many saved recipes (20 limit helps but could be enforced better)

🔧 Recommended Fixes

Critical

// Add fetch timeout and better error handling
async function loadRecipeFromUrl(url) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
  
  try {
    const response = await fetch(url, { 
      signal: controller.signal,
      headers: { 'Accept': 'application/json' }
    });
    clearTimeout(timeoutId);
    
    if (!response.headers.get('content-type')?.includes('application/json')) {
      throw new Error('Response is not JSON');
    }
    // ... rest of function
  } catch (e) {
    clearTimeout(timeoutId);
    if (e.name === 'AbortError') {
      showError('Request timed out');
    } else {
      showError(`Failed to load recipe: ${e.message}`);
    }
  }
}

Enhanced Recipe Validation

function validateRecipe(recipe) {
  if (!recipe || typeof recipe !== 'object') return false;
  if (!recipe.name || typeof recipe.name !== 'string') return false;
  if (!Array.isArray(recipe.steps) || recipe.steps.length === 0) return false;

  // Check steps are sorted and have valid times
  let lastTime = -1;
  for (const step of recipe.steps) {
    if (typeof step.time !== 'number' || step.time < 0) return false;
    if (step.time <= lastTime) return false; // Ensure ascending order
    if (!step.title || typeof step.title !== 'string') return false;
    lastTime = step.time;
  }
  return true;
}

Page Visibility API

// Pause timer when page is hidden
document.addEventListener('visibilitychange', function() {
  if (document.visibilityState === 'hidden') {
    clearInterval(timerInterval);
  } else if (startTime && document.visibilityState === 'visible') {
    updateDisplay();
    timerInterval = setInterval(updateDisplay, 1000);
  }
});

📊 Test Coverage

The application would benefit from:

  • Unit tests for recipe validation logic
  • Integration tests for localStorage persistence
  • End-to-end tests for timer functionality
  • Error scenario testing (network failures, invalid JSON)

🎯 Overall Assessment

This is a high-quality implementation that successfully delivers all requested features. The code is maintainable, user-friendly, and handles most common scenarios well. The identified issues are primarily around edge cases and defensive programming rather than fundamental problems.

Score: 8.5/10 - Excellent work with room for minor security and robustness improvements.


@simonw simonw merged commit be128b3 into main Dec 27, 2025
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants