Skip to content

Add blog-to-newsletter.html - standalone port from Observable#153

Merged
simonw merged 3 commits intomainfrom
claude/port-blog-to-newsletter-01ENssdX7TgTu3LhQAoAxW3b
Dec 14, 2025
Merged

Add blog-to-newsletter.html - standalone port from Observable#153
simonw merged 3 commits intomainfrom
claude/port-blog-to-newsletter-01ENssdX7TgTu3LhQAoAxW3b

Conversation

@simonw
Copy link
Owner

@simonw simonw commented Dec 12, 2025

Port the Observable notebook at observablehq.com/@simonw/blog-to-newsletter
to a standalone HTML tool that works without Observable dependencies.

Features:

  • Fetches blog content from Datasette API (entries, blogmarks, quotations, TILs, notes)
  • Fetches previous newsletter RSS to filter already-sent content
  • Drag-and-drop story reordering
  • Copy rich text newsletter to clipboard
  • Copy just links/quotes/TILs option
  • Markdown rendering for blogmarks and notes
  • Cutoff comment support
  • Search previous newsletter links

Port the Observable notebook at observablehq.com/@simonw/blog-to-newsletter
to a standalone HTML tool that works without Observable dependencies.

Features:
- Fetches blog content from Datasette API (entries, blogmarks, quotations, TILs, notes)
- Fetches previous newsletter RSS to filter already-sent content
- Drag-and-drop story reordering
- Copy rich text newsletter to clipboard
- Copy just links/quotes/TILs option
- Markdown rendering for blogmarks and notes
- Cutoff comment support
- Search previous newsletter links
@claude
Copy link
Contributor

claude bot commented Dec 12, 2025

Claude finished @simonw's task —— View job


Code Review Complete

I've thoroughly reviewed the blog-to-newsletter.html file. Overall, this is a well-crafted standalone HTML application that successfully ports Observable notebook functionality. Here's my detailed feedback:

Todo List:

  • Read the blog-to-newsletter.html file
  • Check for repository-specific guidelines (CLAUDE.md)
  • Analyze code quality and best practices
  • Check for potential bugs and issues
  • Review performance considerations
  • Assess security concerns
  • Evaluate test coverage needs
  • Provide comprehensive review feedback

Strengths

  1. Clean Architecture: Well-organized separation of HTML, CSS, and JavaScript
  2. Responsive Design: Proper mobile-friendly CSS with flexbox and responsive breakpoints
  3. User Experience: Good loading states, error handling, and visual feedback
  4. Drag & Drop Implementation: Robust drag-and-drop functionality with visual indicators

⚠️ Security Concerns

HIGH PRIORITY - Line 756: Deprecated document.execCommand('copy')

document.execCommand('copy'); // Line 756
  • Issue: Uses deprecated document.execCommand which has security and compatibility issues
  • Fix: Replace with modern Clipboard API:
navigator.clipboard.writeText(tempElement.textContent || tempElement.innerText);

MEDIUM - XSS Prevention: Lines 771, 578, 581, etc.

  • Issue: Multiple instances of direct HTML insertion without sanitization
  • Examples: innerHTML = html (line 777), template literals with user data
  • Fix: Consider DOMPurify for HTML sanitization or use textContent where appropriate

🐛 Potential Bugs

  1. Error Handling - Line 488: Silently catches fetch errors
} catch (e) {
  console.log('Cloudflare worker failed, trying GitHub backup'); // Line 489
}
  • Issue: Network errors aren't surfaced to users
  • Fix: Add user-visible fallback messaging
  1. Race Conditions - Lines 785-794: Multiple async operations without coordination
numDaysInput.addEventListener('change', async () => { // Line 785
  // No debouncing or request cancellation
  • Fix: Add debouncing and request cancellation for rapid input changes
  1. Memory Leaks - Line 745: Temporary DOM elements
const tempElement = document.createElement('div'); // Line 745
document.body.appendChild(tempElement); // Line 746
  • Issue: If copy operation fails, element may not be removed
  • Fix: Add try/finally to ensure cleanup

🚀 Performance Considerations

  1. Large Dataset Handling - Line 770: No pagination for large link lists
filtered.slice(0, 100).forEach(url => { // Line 770
  • Good: Already implements basic pagination (100 items)
  • Improvement: Add virtual scrolling for very large datasets
  1. SQL Query Size - Lines 321-465: Very large embedded SQL query
  • Issue: 144-line SQL query in JavaScript affects readability and maintainability
  • Suggestion: Consider breaking into smaller, composable queries or moving to external file
  1. Event Handler Efficiency - Lines 642-681: Complex drag event handlers
  • Issue: Multiple DOM queries during drag operations
  • Fix: Cache DOM references and use event delegation

📋 Code Quality & Best Practices

GOOD PRACTICES:

  • Proper error boundaries and user feedback
  • Semantic HTML structure
  • Accessible form controls with labels
  • CSS custom properties would improve maintainability

IMPROVEMENTS:

  1. Constants - Lines 321, 484, 493: Hard-coded URLs
const DATASETTE_URL = 'https://datasette.simonwillison.net/simonwillisonblog.json';
const WORKER_URL = 'https://restless-cherry-7938.simonw.workers.dev/';
  1. Function Decomposition - Line 688: Large generateNewsletter() function (53 lines)
  • Fix: Break into smaller, focused functions
  1. Magic Numbers - Various lines: Hard-coded values like 100, 60, 2000
  • Fix: Extract to named constants

🧪 Test Coverage

MISSING: No automated tests detected

  • Recommendations:
    • Unit tests for data parsing functions (parseRSS, filterContent)
    • Integration tests for API calls with mock responses
    • UI tests for drag-and-drop functionality
    • Cross-browser compatibility tests for clipboard operations

📝 Minor Issues

  1. Line 289: External CDN dependency without integrity check
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  • Fix: Add SRI hash for security
  1. Lines 516-527: Complex XPath namespace handling
  • Suggestion: Consider using simpler DOM methods for RSS parsing

🎯 Recommendations

  1. Immediate: Replace deprecated document.execCommand with Clipboard API
  2. High Priority: Add input sanitization for XSS prevention
  3. Medium: Implement request debouncing and proper error surfacing
  4. Low: Extract constants and break down large functions

This is a solid implementation that demonstrates good JavaScript practices and user experience design. The main concerns are security-related (deprecated APIs, XSS risks) and should be addressed before production use.

- Add visual drag handle indicator (⋮⋮) on each item
- Better visual feedback with blue drop indicator line
- Smooth transitions and hover/active states
- Full touch device support with touchstart/touchmove/touchend
- Prevent text selection during drag operations
- Handle edge cases for dragging to list boundaries
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 12, 2025

Deploying tools with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0d156a7
Status: ✅  Deploy successful!
Preview URL: https://1ce83fcf.tools-b1q.pages.dev
Branch Preview URL: https://claude-port-blog-to-newslett.tools-b1q.pages.dev

View logs

@claude
Copy link
Contributor

claude bot commented Dec 12, 2025

Claude finished @simonw's task —— View job


Code Review Complete

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read and analyze blog-to-newsletter.html
  • Review code quality and best practices
  • Identify potential bugs and issues
  • Assess performance considerations
  • Evaluate security concerns
  • Check test coverage needs
  • Provide comprehensive review feedback

Overall Assessment

This is a well-crafted standalone HTML application that successfully ports Observable notebook functionality. The code demonstrates good JavaScript practices, responsive design, and thoughtful user experience. However, there are several security concerns and potential improvements to address.

✅ Strengths

  • Clean Architecture: Well-organized separation of HTML, CSS, and JavaScript
  • Responsive Design: Mobile-friendly CSS with proper breakpoints and touch support
  • User Experience: Good loading states, error handling, and visual feedback
  • Drag & Drop: Robust implementation with both mouse and touch support
  • Accessibility: Proper form labels and semantic HTML structure

🔴 Critical Security Issues

1. Deprecated Clipboard API (Line 902)

document.execCommand('copy'); // Line 902

Risk: High - Uses deprecated API with security and compatibility issues
Fix: Replace with modern Clipboard API:

await navigator.clipboard.writeText(tempElement.textContent || tempElement.innerText);

2. XSS Vulnerabilities (Multiple Lines)

Risk: Medium-High - Direct HTML insertion without sanitization
Examples:

  • Line 886: previewEl.innerHTML = html
  • Line 924: linksTable.innerHTML = html
  • Lines 603, 606: Template literals with user data

Fix: Use DOMPurify for HTML sanitization or textContent where appropriate

3. External CDN without Integrity (Line 314)

<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

Risk: Medium - Potential for supply chain attacks
Fix: Add SRI hash for security verification

🟡 Potential Bugs

1. Silent Error Handling (Line 513)

} catch (e) {
  console.log('Cloudflare worker failed, trying GitHub backup'); // Line 514
}

Issue: Network errors aren't surfaced to users
Fix: Add user-visible fallback messaging

2. Memory Leak Risk (Lines 891-905)

const tempElement = document.createElement('div'); // Line 891
document.body.appendChild(tempElement); // Line 893

Issue: If copy operation fails, DOM element may not be removed
Fix: Use try/finally to ensure cleanup

3. Race Conditions (Line 932)

numDaysInput.addEventListener('change', async () => { // Line 932

Issue: No debouncing or request cancellation for rapid changes
Fix: Add debouncing and AbortController for request cancellation

🚀 Performance Considerations

1. Large SQL Query (Lines 346-490)

Issue: 144-line embedded SQL affects readability and maintainability
Suggestion: Consider breaking into smaller, composable queries

2. DOM Query Optimization (Lines 642-832)

Issue: Multiple DOM queries during drag operations
Fix: Cache DOM references and use event delegation

3. Large Dataset Handling (Line 917)

filtered.slice(0, 100).forEach(url => { // Line 917

Good: Already implements pagination (100 items)
Improvement: Consider virtual scrolling for very large datasets

📋 Code Quality Issues

1. Magic Numbers

Examples: Hard-coded values like 100, 60, 2000 throughout
Fix: Extract to named constants

2. Large Functions

Example: generateNewsletter() function (Lines 834-887) - 53 lines
Fix: Break into smaller, focused functions

3. Hard-coded URLs (Lines 509, 518)

Fix: Extract to configuration constants

🧪 Testing Recommendations

Missing: No automated tests detected
Recommendations:

  • Unit tests for data parsing functions (parseRSS, filterContent)
  • Integration tests for API calls with mock responses
  • UI tests for drag-and-drop functionality
  • Cross-browser compatibility tests for clipboard operations

🎯 Priority Recommendations

  1. Immediate: Replace document.execCommand with modern Clipboard API
  2. High: Implement input sanitization for XSS prevention
  3. High: Add SRI integrity check to external script
  4. Medium: Implement request debouncing and proper error surfacing
  5. Low: Extract constants and refactor large functions

📝 Minor Issues

  • Complex XPath namespace handling (Lines 541-552) could use simpler DOM methods
  • Consider using CSS custom properties for better maintainability
  • Some accessibility improvements possible (ARIA labels for drag items)

This is a solid implementation that demonstrates good understanding of modern web development practices. The main concerns are security-related and should be addressed before production deployment. The functionality is well-implemented and the user experience is thoughtfully designed.

- Detect URLs longer than 200 characters in the generated newsletter
- Display warning box with list of long URLs and their character counts
- Add "Edit URL" button for each long URL that opens a prompt()
- URL replacements persist and are applied to the newsletter HTML
- Warning auto-hides when no long URLs remain after editing
@claude
Copy link
Contributor

claude bot commented Dec 12, 2025

Claude finished @simonw's task —— View job


Code Review for blog-to-newsletter.html

Todo List:

  • Read repository guidelines (CLAUDE.md) - No CLAUDE.md found
  • Read and analyze blog-to-newsletter.html file
  • Review code quality and best practices
  • Identify potential bugs and issues
  • Assess performance considerations
  • Evaluate security concerns
  • Check test coverage needs
  • Provide comprehensive review feedback

Overall Assessment

This is a well-implemented standalone HTML application that successfully ports Observable notebook functionality. The code demonstrates solid JavaScript fundamentals, responsive design, and thoughtful user experience. However, there are several security vulnerabilities and areas for improvement.

✅ Strengths

  • Clean Architecture: Well-organized separation of HTML, CSS, and JavaScript
  • Responsive Design: Mobile-friendly CSS with proper flexbox, touch events, and responsive breakpoints
  • User Experience: Good loading states, error handling, visual feedback, and drag-and-drop with both mouse and touch support
  • Accessibility: Proper form labels, semantic HTML structure
  • State Management: Well-organized application state with clear data flow
  • Robust Drag & Drop: Complex but well-implemented reordering with visual feedback

🔴 Critical Security Issues

1. Deprecated Clipboard API (Line 1017)

document.execCommand('copy'); // Line 1017 - DEPRECATED

Risk: High - Uses deprecated API with security and compatibility issues
Fix: Replace with modern Clipboard API:

await navigator.clipboard.writeText(tempElement.textContent || tempElement.innerText);

2. XSS Vulnerabilities (Multiple Lines)

Risk: High - Direct HTML insertion without sanitization

  • Line 948: previewEl.innerHTML = html - User-controlled HTML
  • Line 1039: linksTable.innerHTML = html - Constructed HTML with URLs
  • Lines 659, 661, 663: Template literals with user data

Fix: Use DOMPurify for HTML sanitization or textContent where appropriate

3. External CDN without Integrity (Line 366)

<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

Risk: Medium - Supply chain attack potential
Fix: Add SRI hash for security verification

🟠 Potential Bugs & Issues

1. Silent Error Handling (Line 570)

} catch (e) {
  console.log('Cloudflare worker failed, trying GitHub backup'); // Line 571
}

Issue: Network errors aren't surfaced to users
Fix: Add user-visible fallback messaging

2. Memory Leak Risk (Lines 1006-1020)

const tempElement = document.createElement('div'); // Line 1006
document.body.appendChild(tempElement); // Line 1008
// ... no guaranteed cleanup on error

Fix: Use try/finally to ensure cleanup:

try {
  // clipboard operations
} finally {
  if (tempElement.parentNode) {
    document.body.removeChild(tempElement);
  }
}

3. Race Conditions (Line 1047)

numDaysInput.addEventListener('change', async () => { // Line 1047

Issue: Rapid input changes could cause overlapping requests
Fix: Implement debouncing and request cancellation with AbortController

4. Prompt() Usage (Line 992)

const newUrl = prompt('Edit this URL:', url); // Line 992

Issue: prompt() is non-standard UI that can be blocked by browsers
Fix: Implement custom modal dialog

🚀 Performance Considerations

1. Large Embedded SQL (Lines 403-547)

Issue: 144-line SQL query affects maintainability
Improvement: Consider extracting to external file or breaking into composable queries

2. DOM Query Optimization (Lines 730-891)

Issue: Multiple DOM queries during drag operations
Fix: Cache DOM references and use event delegation

3. Large Dataset Handling (Line 1032)

filtered.slice(0, 100).forEach(url => { // Line 1032 - Good pagination

Good: Already implements pagination
Improvement: Consider virtual scrolling for very large datasets

📋 Code Quality Issues

1. Magic Numbers

Examples: Hard-coded values like 100, 200, 2000, 60 throughout
Fix: Extract to named constants:

const CONSTANTS = {
  MAX_DISPLAYED_LINKS: 100,
  LONG_URL_THRESHOLD: 200,
  CLIPBOARD_SUCCESS_TIMEOUT: 2000,
  MAX_DAYS_INPUT: 60
};

2. Large Functions

Examples:

  • generateNewsletter() (lines 892-952) - 60+ lines
  • renderStoryOrder() (lines 699-889) - 190+ lines

Fix: Break into smaller, focused functions

3. Hard-coded URLs (Lines 566, 575)

Fix: Extract to configuration constants for easier maintenance

🧪 Testing Recommendations

Missing: No automated tests detected
Critical Test Areas:

  • Unit tests for data parsing functions (parseRSS, filterContent)
  • Integration tests for API calls with mock responses
  • UI tests for drag-and-drop functionality
  • Cross-browser compatibility tests for clipboard operations
  • Error handling scenarios (network failures, malformed data)

🔧 Minor Improvements

  1. URL Pattern Validation (Line 956): Consider validating URL patterns more strictly
  2. Error Boundary: Add global error handler for unhandled promise rejections
  3. Loading States: More granular loading indicators for different operations
  4. CSS Custom Properties: Would improve theme maintenance
  5. ARIA Labels: Consider adding for drag-and-drop items

🎯 Priority Recommendations

  1. Critical: Replace document.execCommand with Clipboard API
  2. High: Implement HTML sanitization for XSS prevention
  3. High: Add SRI integrity check to external script
  4. Medium: Fix memory leak in clipboard function
  5. Medium: Add request debouncing and proper error surfacing
  6. Low: Extract constants and refactor large functions

This is a solid implementation with good user experience design. The main concerns are security-related and should be addressed before production use, but the core functionality is well-executed and the drag-and-drop implementation is particularly robust.

@simonw simonw merged commit 7c21d21 into main Dec 14, 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