-
-
Save simonw/d5ccbca1b530868980609222790a97cb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| tools % claude | |
| ╭────────────────────────────────────────────────╮ | |
| │ ✻ Welcome to Claude Code research preview! │ | |
| │ │ | |
| │ /help for help │ | |
| │ │ | |
| │ cwd: /Users/simon/Dropbox/Development/tools │ | |
| ╰────────────────────────────────────────────────╯ | |
| > run python build_colophon.py - this produces a colophon.html page. Inspect the first 100 lines of this page to see a bug where <br> tags | |
| are sometimes included inside links | |
| ⏺ Bash(python build_colophon.py)… | |
| ⎿ Colophon page built successfully as colophon.html | |
| ⏺ Read(file_path: "/Users/simon/Dropbox/Development/tools/colophon.html", limit: 100)… | |
| ⎿ <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>tools.simonwillison.net colophon</title> | |
| <style> | |
| body { | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| line-height: 1.5; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 1rem; | |
| color: #1a1a1a; | |
| } | |
| h1 { | |
| border-bottom: 2px solid #f0f0f0; | |
| padding-bottom: 0.5rem; | |
| margin-top: 2rem; | |
| } | |
| h2 { | |
| margin-top: 2rem; | |
| font-size: 1.4rem; | |
| } | |
| a { | |
| color: #0066cc; | |
| text-decoration: none; | |
| } | |
| a:hover { | |
| text-decoration: underline; | |
| } | |
| .tool { | |
| margin-bottom: 2rem; | |
| border-bottom: 1px solid #f0f0f0; | |
| padding-bottom: 1rem; | |
| } | |
| .tool-name { | |
| font-weight: bold; | |
| } | |
| .commit { | |
| background-color: #f8f9fa; | |
| padding: 1rem; | |
| margin: 0.75rem 0; | |
| border-radius: 4px; | |
| border-left: 3px solid #ddd; | |
| } | |
| .commit-hash { | |
| font-family: monospace; | |
| color: #666; | |
| font-size: 0.85rem; | |
| } | |
| .commit-date { | |
| color: #666; | |
| font-size: 0.85rem; | |
| margin-left: 0.5rem; | |
| } | |
| .commit-message { | |
| margin-top: 0.5rem; | |
| } | |
| .urls { | |
| margin-top: 1rem; | |
| } | |
| @media (max-width: 600px) { | |
| body { | |
| padding: 0.5rem; | |
| } | |
| .commit { | |
| padding: 0.75rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>tools.simonwillison.net colophon</h1> | |
| <p>The tools on <a href="https://tools.simonwillison.net/">tools.simonwillison.net</a> were mostly built using <a | |
| href="https://simonwillison.net/tags/ai-assisted-programming/">AI-assisted programming</a>.</p> | |
| <p>This page lists the commit messages for each tool, many of which link to the LLM transcript used to produce the code.</p> | |
| <div class="tool"> | |
| <h2 class="tool-name"><a href="https://tools.simonwillison.net/social-media-cropper">social-media-cropper.html</a></h2> | |
| <div class="commit"> | |
| <div> | |
| <a href="https://github.com/simonw/tools/commit/ | |
| b4a2bc98753c3b6592a7cd86a9510bc6bf552efb" class="commit-hash" target="_blank"> | |
| b4a2bc</a> | |
| <span class="commit-date">December 10, 2024 20:35</span> | |
| </div> | |
| <div class="commit-message">Social media cropper<br><br><a | |
| href="https://gist.github.com/simonw/12b8f88932a71450071190e1289a17e9<br>" | |
| target="_blank">https://gist.github.com/simonw/12b8f88932a71450071190e1289a17e9<br></a></div> | |
| </div> | |
| <div class="commit"> | |
| <div> | |
| <a href="https://github.com/simonw/tools/commit/ | |
| a109547bd328397b4eb7160cd1263f533cce3ea1" class="commit-hash" target="_blank"> | |
| a10954</a> | |
| <span class="commit-date">February 28, 2025 16:02</span> | |
| </div> | |
| <div class="commit-message">Support 2:1 and 14:10 ratios<br><br><a | |
| href="https://gist.github.com/simonw/e23917eddcbb368c9b6180d581f8f40a<br>" | |
| target="_blank">https://gist.github.com/simonw/e23917eddcbb368c9b6180d581f8f40a<br></a></div> | |
| </div> | |
| ⏺ I found the bug. There are <br> tags inside the link URLs on lines 88 and 98, causing the links to break. The links should be a single | |
| URL without any line breaks inside the <a> tags. | |
| > fix it | |
| ⏺ Read(file_path: "/Users/simon/Dropbox/Development/tools/build_colophon.py")… | |
| ⎿ #!/usr/bin/env python3 | |
| import json | |
| import re | |
| from datetime import datetime | |
| import html | |
| from pathlib import Path | |
| def linkify_urls(text): | |
| """Convert URLs in text to clickable links.""" | |
| url_pattern = r"(https?://[^\s]+)" | |
| return re.sub(url_pattern, r'<a href="\1" target="_blank">\1</a>', text) | |
| def format_commit_message(message): | |
| """Format commit message with line breaks and linkified URLs.""" | |
| # Escape HTML entities | |
| escaped = html.escape(message) | |
| # Convert newlines to <br> | |
| with_breaks = escaped.replace("\n", "<br>") | |
| # Linkify URLs | |
| return linkify_urls(with_breaks) | |
| def build_colophon(): | |
| # Load the gathered_links.json file | |
| try: | |
| with open("gathered_links.json", "r") as f: | |
| data = json.load(f) | |
| except FileNotFoundError: | |
| print("Error: gathered_links.json not found. Run gather_links.py first.") | |
| return | |
| pages = data.get("pages", {}) | |
| if not pages: | |
| print("No pages found in gathered_links.json") | |
| return | |
| # Sort pages by most recent commit date (newest first) | |
| def get_most_recent_date(page_data): | |
| commits = page_data.get("commits", []) | |
| if not commits: | |
| return "0000-00-00T00:00:00" | |
| # Find the most recent commit date | |
| dates = [commit.get("date", "0000-00-00T00:00:00") for commit in commits] | |
| return max(dates) if dates else "0000-00-00T00:00:00" | |
| sorted_pages = sorted( | |
| pages.items(), key=lambda x: get_most_recent_date(x[1]), reverse=True | |
| ) | |
| # Start building the HTML | |
| html_content = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>tools.simonwillison.net colophon</title> | |
| <style> | |
| body { | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| line-height: 1.5; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 1rem; | |
| color: #1a1a1a; | |
| } | |
| h1 { | |
| border-bottom: 2px solid #f0f0f0; | |
| padding-bottom: 0.5rem; | |
| margin-top: 2rem; | |
| } | |
| h2 { | |
| margin-top: 2rem; | |
| font-size: 1.4rem; | |
| } | |
| a { | |
| color: #0066cc; | |
| text-decoration: none; | |
| } | |
| a:hover { | |
| text-decoration: underline; | |
| } | |
| .tool { | |
| margin-bottom: 2rem; | |
| border-bottom: 1px solid #f0f0f0; | |
| padding-bottom: 1rem; | |
| } | |
| .tool-name { | |
| font-weight: bold; | |
| } | |
| .commit { | |
| background-color: #f8f9fa; | |
| padding: 1rem; | |
| margin: 0.75rem 0; | |
| border-radius: 4px; | |
| border-left: 3px solid #ddd; | |
| } | |
| .commit-hash { | |
| font-family: monospace; | |
| color: #666; | |
| font-size: 0.85rem; | |
| } | |
| .commit-date { | |
| color: #666; | |
| font-size: 0.85rem; | |
| margin-left: 0.5rem; | |
| } | |
| .commit-message { | |
| margin-top: 0.5rem; | |
| } | |
| .urls { | |
| margin-top: 1rem; | |
| } | |
| @media (max-width: 600px) { | |
| body { | |
| padding: 0.5rem; | |
| } | |
| .commit { | |
| padding: 0.75rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>tools.simonwillison.net colophon</h1> | |
| <p>The tools on <a href="https://tools.simonwillison.net/">tools.simonwillison.net</a> were mostly built using <a | |
| href="https://simonwillison.net/tags/ai-assisted-programming/">AI-assisted programming</a>.</p> | |
| <p>This page lists the commit messages for each tool, many of which link to the LLM transcript used to produce the code.</p> | |
| """ | |
| # Add each page with its commits | |
| for page_name, page_data in sorted_pages: | |
| tool_url = f"https://tools.simonwillison.net/{page_name.replace('.html', '')}" | |
| commits = page_data.get("commits", []) | |
| # Reverse the commits list to show oldest first | |
| commits = list(reversed(commits)) | |
| html_content += f""" | |
| <div class="tool"> | |
| <h2 class="tool-name"><a href="{tool_url}">{page_name}</a></h2> | |
| """ | |
| # Add each commit | |
| for commit in commits: | |
| commit_hash = commit.get("hash", "") | |
| short_hash = commit_hash[:7] if commit_hash else "unknown" | |
| commit_date = commit.get("date", "") | |
| # Format the date with time | |
| formatted_date = "" | |
| if commit_date: | |
| try: | |
| dt = datetime.fromisoformat(commit_date) | |
| formatted_date = dt.strftime("%B %d, %Y %H:%M") | |
| except ValueError: | |
| formatted_date = commit_date | |
| commit_message = commit.get("message", "") | |
| formatted_message = format_commit_message(commit_message) | |
| commit_url = f"https://github.com/simonw/tools/commit/{commit_hash}" | |
| html_content += f""" | |
| <div class="commit"> | |
| <div> | |
| <a href="{commit_url}" class="commit-hash" target="_blank">{short_hash}</a> | |
| <span class="commit-date">{formatted_date}</span> | |
| </div> | |
| <div class="commit-message">{formatted_message}</div> | |
| </div> | |
| """ | |
| html_content += """ | |
| </div> | |
| """ | |
| # Close the HTML | |
| html_content += """ | |
| </body> | |
| </html> | |
| """ | |
| # Write the HTML to colophon.html | |
| with open("colophon.html", "w") as f: | |
| f.write(html_content) | |
| print("Colophon page built successfully as colophon.html") | |
| if __name__ == "__main__": | |
| build_colophon() | |
| ⏺ Update(file_path: /Users/simon/Dropbox/Development/tools/build_colophon.py)… | |
| ⎿ Updated /Users/simon/Dropbox/Development/tools/build_colophon.py with 7 additions and 10 removals | |
| 6 from pathlib import Path | |
| 7 | |
| 8 | |
| 9 def linkify_urls(text): | |
| 10 """Convert URLs in text to clickable links.""" | |
| 11 url_pattern = r"(https?://[^\s]+)" | |
| 12 return re.sub(url_pattern, r'<a href="\1" target="_blank">\1</a>', text) | |
| 13 | |
| 14 | |
| 9 def format_commit_message(message): | |
| 10 """Format commit message with line breaks and linkified URLs.""" | |
| 11 # Escape HTML entities | |
| 12 escaped = html.escape(message) | |
| 13 # Convert newlines to <br> | |
| 14 with_breaks = escaped.replace("\n", "<br>") | |
| 15 # Linkify URLs | |
| 16 return linkify_urls(with_breaks) | |
| 13 | |
| 14 # Linkify URLs first (before adding breaks) | |
| 15 url_pattern = r"(https?://[^\s]+)" | |
| 16 linkified = re.sub(url_pattern, r'<a href="\1" target="_blank">\1</a>', escaped) | |
| 17 | |
| 18 # Then convert newlines to <br> | |
| 19 return linkified.replace("\n", "<br>") | |
| 20 | |
| 21 | |
| 22 def build_colophon(): | |
| ⏺ Bash(python build_colophon.py)… | |
| ⎿ Colophon page built successfully as colophon.html | |
| ⏺ Read(file_path: "/Users/simon/Dropbox/Development/tools/colophon.html", limit: 100)… | |
| ⎿ <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>tools.simonwillison.net colophon</title> | |
| <style> | |
| body { | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| line-height: 1.5; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 1rem; | |
| color: #1a1a1a; | |
| } | |
| h1 { | |
| border-bottom: 2px solid #f0f0f0; | |
| padding-bottom: 0.5rem; | |
| margin-top: 2rem; | |
| } | |
| h2 { | |
| margin-top: 2rem; | |
| font-size: 1.4rem; | |
| } | |
| a { | |
| color: #0066cc; | |
| text-decoration: none; | |
| } | |
| a:hover { | |
| text-decoration: underline; | |
| } | |
| .tool { | |
| margin-bottom: 2rem; | |
| border-bottom: 1px solid #f0f0f0; | |
| padding-bottom: 1rem; | |
| } | |
| .tool-name { | |
| font-weight: bold; | |
| } | |
| .commit { | |
| background-color: #f8f9fa; | |
| padding: 1rem; | |
| margin: 0.75rem 0; | |
| border-radius: 4px; | |
| border-left: 3px solid #ddd; | |
| } | |
| .commit-hash { | |
| font-family: monospace; | |
| color: #666; | |
| font-size: 0.85rem; | |
| } | |
| .commit-date { | |
| color: #666; | |
| font-size: 0.85rem; | |
| margin-left: 0.5rem; | |
| } | |
| .commit-message { | |
| margin-top: 0.5rem; | |
| } | |
| .urls { | |
| margin-top: 1rem; | |
| } | |
| @media (max-width: 600px) { | |
| body { | |
| padding: 0.5rem; | |
| } | |
| .commit { | |
| padding: 0.75rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>tools.simonwillison.net colophon</h1> | |
| <p>The tools on <a href="https://tools.simonwillison.net/">tools.simonwillison.net</a> were mostly built using <a | |
| href="https://simonwillison.net/tags/ai-assisted-programming/">AI-assisted programming</a>.</p> | |
| <p>This page lists the commit messages for each tool, many of which link to the LLM transcript used to produce the code.</p> | |
| <div class="tool"> | |
| <h2 class="tool-name"><a href="https://tools.simonwillison.net/social-media-cropper">social-media-cropper.html</a></h2> | |
| <div class="commit"> | |
| <div> | |
| <a href="https://github.com/simonw/tools/commit/ | |
| b4a2bc98753c3b6592a7cd86a9510bc6bf552efb" class="commit-hash" target="_blank"> | |
| b4a2bc</a> | |
| <span class="commit-date">December 10, 2024 20:35</span> | |
| </div> | |
| <div class="commit-message">Social media cropper<br><br><a | |
| href="https://gist.github.com/simonw/12b8f88932a71450071190e1289a17e9" | |
| target="_blank">https://gist.github.com/simonw/12b8f88932a71450071190e1289a17e9</a><br></div> | |
| </div> | |
| <div class="commit"> | |
| <div> | |
| <a href="https://github.com/simonw/tools/commit/ | |
| a109547bd328397b4eb7160cd1263f533cce3ea1" class="commit-hash" target="_blank"> | |
| a10954</a> | |
| <span class="commit-date">February 28, 2025 16:02</span> | |
| </div> | |
| <div class="commit-message">Support 2:1 and 14:10 ratios<br><br><a | |
| href="https://gist.github.com/simonw/e23917eddcbb368c9b6180d581f8f40a" | |
| target="_blank">https://gist.github.com/simonw/e23917eddcbb368c9b6180d581f8f40a</a><br></div> | |
| </div> | |
| ⏺ Fixed! The order of operations was the issue. I changed the code to: | |
| 1. First escape HTML entities | |
| 2. Then link the URLs | |
| 3. Finally replace newlines with <br> tags | |
| This way, the URL linking happens before newline replacement, preventing <br> tags from being included inside the URL links. | |
| ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ | |
| │ > │ | |
| ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ | |
| Press Ctrl-C again to exit \⏎ for newline | |
| Total cost: $0.1144 | |
| Total duration (API): 30.5s | |
| Total duration (wall): 1m 18.2s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment