

[

	{
		"title": "<file-preview> - A simple web component for file previews",
		"date":"Tue Aug 11 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1786471200,
		"url":"https://www.raymondcamden.com/2026/08/11/file-preview-a-simple-web-component-for-file-previews",
		"content":"This was a &quot;I can't fall asleep and my mind is wondering&quot; type of idea and honestly, I'm surprised at how good it came out. For a while now I've shared demos where a user picks an image and ... something. It doesn't matter. But I typically add a preview of the image when it's selected. This works because once the user selects a file on their local system, JavaScript can then access the bits of that file, making it trivial then to render it. The idea I had, late at night, was simple - what if this could be a web component and expanded to cover different file types?\nIn my imagination, it would look something like this in code:\n\nThe component takes one attribute, file-id, which points to the ID value of the field to monitor. As for preview support, I figured I could cover these:\n\nImages (of course)\nHTML, .txt\nMarkdown (after converting to HTML)\nOffice docs (Using the excellent officeParser library)\nPDF (ditto)\nAudio and Video (using the browser's built-in support)\n\nAnd more. I decided to let Cursor take a swing at this, so I started with this prompt:\nI want to create a web component that does the following:\n\nIt's tied to a input[type=file] control. When the user selects a file, it attempts to preview it. \n\nIt looks like so: &lt;file-preview file-id=&quot;id of file field&quot;&gt;&lt;/file-preview&gt;\n\nThe user would position/size it with CSS as they see fit. The component would support previews for:\n\n* Office docs and PDFs via https://officeparser.harshankur.com/. This will be lazy loaded when a matching file type is selected. It will render the HTML version\n* HTML docs - as is.\n* Markdown - parse to HTML via the `marked` library and render the HTML\n* Images - render as usual\n* Video and Audio - create a new instance of &lt;video&gt; or &lt;audio&gt; so the user could click play to preview\n\nAnything else I'm missing?\n\nCursor came back with a bunch of suggestions, including supporting text type documents (JSON, txt, etc) and said it should handle noting when the associated file field was reset.\nAnd that was pretty much it. In the first iteration there was a bug with the iframe it used to render a PDF. This was due to the iframe's sandbox value and easily corrected.\nCursor's agent also built a good demo (https://cfjedimaster.github.io/file-preview/index.html) but it was focused on the component, and not a &quot;real&quot; form.\nI returned to Cursor with this prompt:\nthe demo you built in index.html is ok, but the idea here was to \ninclude the web component in a form. make a new file, demo.html, \nand have a few form fields in it, one of which is the file \npreview. just have it post to nothing that's fine\n\nAnd that created a better demo (imo!):\n\nYou can see the full code at the repo here: https://github.com/cfjedimaster/file-preview. If enough people ask (ok, let's say 2), I'll put this up on NPM for easier installation.\n",
		"tags":[
	        
            "web components",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (8/2/26)",
		"date":"Sun Aug 02 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1785693600,
		"url":"https://www.raymondcamden.com/2026/08/02/links-for-you-8226",
		"content":"Happy August folks. Louisiana is celebrating the approach of Fall by gradually increasing the temperatures to roughly that of the surface of the sun. Last night we saw the new Spider-Man, which was really good, although not as fantastic as &quot;No Way Home&quot;. The point of these posts is for me to share content from other folks, but I'll remind folks of my Youtube channel. This past week I shared a few new videos demonstrating some cool stuff with Cursor so if you've not checked them out yet, give me a quick watch and let me know what you think!\nWeb browsing on your gaming console\nFirst up is a great post by Vale detailing the capabilities and history of web browsers on game consoles. I've been playing on consoles since the very beginning and a lot of this was still new and surprising to me. Honestly I can't imagine surfing any web page on a browser now. I think the last time I did was on my XBox so I could find a cool background picture.\nAudio visualization with wavesurfer.js\nNext up is a cool JavaScript library for audio visualization, wavesurfer.js. Also I think this is the first site I've seen on the xyz TLD. The library works pretty simply - point it at a div and MP3 and it visualizes it on your web page. Here's a simple example of it in action.\n\n  See the Pen \n  wavesurfer.js by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWhat the hell happened to frontend?\nAh, the good ole days, hand crafting HTML in Notepad and manually &quot;deploying&quot; via carefully selecting and uploading via an FTP client. In the following few decades things got... a little bit more complex. This great deep dive by David Poblador i Garcia goes into detail over the changes in frontend. Those of you new to this - you missed a lot. ;)\nJust For Fun\nWith AI being everywhere, I'm curious how many of my readers ever saw Electric Dreams, an incredibly corny 80s film I remember watching on VHS over and over again. The story is... well... it was the 80s so that gives you an idea, but it had a pretty good songs in it including the main theme. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Updating my Watch Site for Algolia Support",
		"date":"Wed Jul 29 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1785348000,
		"url":"https://www.raymondcamden.com/2026/07/29/updating-my-watch-site-for-algolia-support",
		"content":"Alright, so I was supposed to share this a bit quicker, but life, work, etc, kept me busy. I've had this particular change up for a few days but I'm just now getting around to blogging about it. What changes?\n\nAdding Algolia's MCP to my Cursor environment\nUsing Algolia's MCP (what worked, what did not)\nAdding Algolia content as a scheduled service with Netlify\nAdding a basic search front end to I Watch Watches\n\nBefore going on, be sure you've read the last piece where I talked about agentic development in general, and specifically how I built the site.\nIf you don't have a log, did it happen?\nI've been doing some work with different profiles and my Cursor environment, and in doing so, I lost the chat I had with Cursor about these features. I know it's there but I think I lost it the transition/testing I've been doing. (I did find the chat actually, but I'll still probably skip including it directly here.)\nThe Plan - in Two Parts\nI spelled this out above, but it bears repeating. I wanted to add search to my site and wanted to use Algolia. Algolia makes use of an index that's a copy of your site data. For me that's the RSS feed items from the watch blogs I aggregate. Creating and setting up the index is a one time operation. Populating the index and keeping it up to date is something you do forever. (To be technical, you've got a lot of options in that regard. So for example, in a site with millions of updates you could hold off updating your index until a particular time every day or other time period.) So the first operation was setting up that process.\nThe second part is much simpler - wiring up the search UI. For that I used my interface here as a guide - a nav search field that loads up a search page with results, and another form there so you can quickly change your search.\nAlgolia and MCP\nAlgolia's MCP docs were a bit confusing to me. The first example mentioned is the &quot;Algolia Public MCP&quot; - but this is the &quot;per app&quot; MCP I talked about here: &quot;TIL - Algolia Makes Creating an MCP Server Stupid Easy&quot;. In other words, it's an MCP server for your data.\nInstead, you want the Algolia Productivity MCP, which is what's used to work with your account at a high level. But - right away you see:\n&quot;Read-only index access, with an extended set of tools such as analytics.&quot;\nSo the MCP server can't actually make changes to your account, like creating or modifying an index. That's a bit of a bummer (and I let my buddy at Algolia know this), but it does give you MCP access via your agent so you can do things like list your indexes and perform searches right from your AI harness.\nHere's a test I did in Cursor:\n\n\n\nPretty standard stuff, but notice both Cursor and Algolia's MCP gracefully handled the fact that I didn't specify a particular index for my account. It was smart enough to recognize which index would most likely have rolex as a term.\nOne more nit to be aware of. You have to enable Algolia MCP. This is documented, but given that the URL is standard (not something special per index, it's for Algolia in general) and given you have to authenticate to use it, it felt weird that I also had to enable it in my account. This is also feedback I shared with my contact at Algolia.\nSo given I couldn't use the MCP to create the index, I just did it myself. By hand. Uphill. In the snow. Both ways. After the 20 to 30 seconds of that work was done, I was ready to start building.\nIndex Seeding and Updating\nMy first choice was a bit difficult. For my blog here, I've got a huge amount of content, and keeping Algolia in sync could be a bit complex. I went with an approach that gets recent items and does an upsert to my index using the batch API.\nFor I Watch Watches, I decided to use the same API, the same upsert, but to pass everything. My spidey-sense tells me this may be problematic later as the aggregator grows, and I'll probably have to go with a &quot;last 1k&quot; or so filter, but for now it worked.\nIn Cursor, I described my ask - create a Netlify scheduled function to upsert my data to my Algolia index. Netlify has my Algolia secrets and serverless functions can use it, so I set Cursor to building it out, and it did a great job. In fact, it did it a bit differently than I would.\nFirst, it created a core Algolia module in src/lib/algolia.ts:\n\nAnd then it built the serverless function which ended up being much smaller with the core logic abstracted.\n\nRunning locally, I was able to use curl to run this function and then the Algolia MCP to search and confirm it actually worked.\nSearching\nAlgolia has multiple options for performing searches, but for me the basic JavaScript SDK is all I need. I asked Cursor to implement that following the pattern I have on my blog here and as I described above. Cursor wired up the search in the top nav, built a new page, search.astro, and mostly followed the same pattern I did here. Here's the complete Astro page:\n\nIn case you're curious, the key embedded in cod",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building Agentically and Celebrating Watches",
		"date":"Fri Jul 24 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1784916000,
		"url":"https://www.raymondcamden.com/2026/07/24/building-agentically-and-celebrating-watches",
		"content":"I've shared past experiences with vibe coding and it's been a fascinating way to experiment and build on a small scale, but while it works well for POCs and other small utilities (I won't say 'toys' as I've built useful, if small, things now multiple times), it isn't really a &quot;process&quot; and not one that would work over a project with a longer time frame. This is where &quot;Agentic Development&quot; comes in. It's not always referred to as such and it's a huge topic, but let me give you my opinion on it and then demonstrate how I used it to build my newest project, I Watch Watches.\nAgentic Development in a Nutshell\nVibe coding is traditionally defined by a quick prompt passed to an AI agent. There may be a few rounds of changes afterwards, but the process is relatively simple and straightforward. Acceptable for a team of one and smaller projects, but not necessarily a &quot;real&quot; enterprise/larger environment.\nAgentic development integrates your AI agent or harness across an entire development process. Broadly speaking it entails:\n\nA planning stage where you may start with a prompt, but the agent doesn't write any code, but instead creates a plan. That plan is shared with you and you have the opportunity to adjust as you see fit. Add something, remove something, tweak something, this is where you and the agent agree on not just what is being done, but how. For developers who worry about AI taking your job, this is where your experience comes into play. I always validate that the plan makes sense and is building how I (or my organization) would build.\nAn implementation phase where the AI agent takes over - but even here the process can be far different from simple vibe coding. A good harness (like Cursor!) will let you define rules (&quot;always do X&quot;) as well as skills (&quot;when doing Y, here is how we do it&quot;) which lets you give firm guardrails for how code will be generated. This is especially useful in cases where you are working on a team within a large codebase and need to ensure you follow the established procedures for that project.\nValidation is also part of the process. A good agent/harness will not just build something but also validate that it actually worked. This could also be combined with creating tests to cover new features.\nOn top of validating the change worked, you can also add an additional level of verification, so for example, when a PR is submitted, you can use an agent (at Cursor this is Bugbot, but of course other options exist).\n\nThat's a pretty high level overview, but the end result is that you've got AI agents working for you in a much more controlled, and reliable way. Honestly it's already changed how I do a lot of my work even in cases where I've written every bit of code. Having the agent there to help validate and check what I've done is like having a virtual intern who never complains.\nSo, how about an example?\nWatches are my bling\nI've been a huge fan of watches for pretty much all my life. I remember Swatches as a kid (I literally just signed up for a pre-order for a new one) and as I got older, I started collecting more and more watches and finding myself stopping at jewelry stores just to peruse their collections. I've got some cheap ones, some expensive ones, and some weird ones, but I love them all.\nThis is my current favorite and I've got it on right now: Memento Mori Rose\n\n\n\nYes, this is a Camden watch, and the name initially caught my attention, but their collection is incredible (I have two). Even better, they literally check the timezone of where their watches are being shipped and will set the watch to the appropriate time.\nMy typical way of discovering new watches is either seeing it on a person or seeing it on Facebook. Shockingly, if you Like a few watch ads, you end up seeing a lot more, and honestly I'm fine with that. (Facebook is where I first saw the Camden Watch company.) A few days ago it finally clicked that there's probably a decent amount of watch blogs out there, why not start aggregating them so I can see watches my own way - in my own app.\nHow I Started\nSo obviously I'm using Cursor (where I work, just as a reminder!) and it's got a dedicated Plan mode. I switched to that and started off with this prompt:\n \n\n\n\n.markdown-body {\npadding: 20px;\nmargin-bottom: 30px;\n}\n\n\n  \nI want to build an RSS aggregator dedicated to watches. This is for consumers, people who love watches. The stack should be:\nNetlify for hosting\nNetlify Database to store a table of feeds (name of site, url to site, rss url to site) and a table of feed items. That table drives the UI and is also used to recognized new versus existing feed items.\nAstro 7.x (latest Astro)\nFront end code should be minimal as it's just rendering database items, but if any code is needed, use vanilla JS or Alpine.js if the code gets complex (again, i tshouldn't)\nthe app right now will one public page, the main page showing feed items aggregated. generally an aggregator is a li",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Copying HTML Tables as Text, Markdown, and CSV",
		"date":"Mon Jul 20 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1784570400,
		"url":"https://www.raymondcamden.com/2026/07/20/copying-html-tables-as-text-markdown-and-csv",
		"content":"Earlier today and in a non web-related conversation, I saw someone talk about copying tabular data into different formats, like plain text and CSV. It got me thinking - this could be a useful feature for web sites and surely something pretty trivial to do in JavaScript. So I whipped up a quick demo.\nFirst, I added a table of course:\n\nAlright, so to enable this feature, I thought data attributes might be nice. This would let you use it on anything, a button, a link, image, etc. The data attribute would be responsible for pointing to the table (via an id) and specifying a format. Here's an example:\n\nNow all I need to do is wire it up to code that looks for data-table and adds the appropriate handlers. The first part is trivial:\n\nNote that while I named the variable links, you can still use this with buttons or images.\nI loop over each match and first get the table referenced, throwing an error in console if it doesn't exist:\n\nNext, I check the type, defaulting to text:\n\nThe final bit is to assign the click handler:\n\nFor each, I get the raw data from the HTML table, convert it to the right format, and then write to the clipboard. (Check out my article on working with the clipboard in JavaScript.)\ngetRawData just iterates through the table's DOM creating a 2D array:\n\nformatData is a bit more complex and I used AI to help me with each of the main sections. I added support for plain text, Markdown, and CSV:\n\nWith my test table (and I'm stripping out a bit here for brevity), here's the plain text version:\n+----------+------------+--------+----------------------+\n| Name     | DOB        | Gender | Breed                |\n+----------+------------+--------+----------------------+\n| Whiskers | 2019-03-14 | Male   | Maine Coon           |\n| Luna     | 2020-07-22 | Female | Siamese              |\n| Oliver   | 2018-11-05 | Male   | British Shorthair    |\n| Max      | 2019-06-25 | Male   | Sphynx               |\n| Stella   | 2021-05-04 | Female | American Curl        |\n| Rocky    | 2018-09-20 | Male   | Burmese              |\n| Molly    | 2022-01-11 | Female | LaPerm               |\n+----------+------------+--------+----------------------+\n\nHere's the Markdown:\n| Name | DOB | Gender | Breed |\n| --- | --- | --- | --- |\n| Whiskers | 2019-03-14 | Male | Maine Coon |\n| Luna | 2020-07-22 | Female | Siamese |\n| Oliver | 2018-11-05 | Male | British Shorthair |\n| Max | 2019-06-25 | Male | Sphynx |\n| Stella | 2021-05-04 | Female | American Curl |\n| Rocky | 2018-09-20 | Male | Burmese |\n| Molly | 2022-01-11 | Female | LaPerm |\n\nAnd here's CSV:\nName,DOB,Gender,Breed\nWhiskers,2019-03-14,Male,Maine Coon\nLuna,2020-07-22,Female,Siamese\nOliver,2018-11-05,Male,British Shorthair\nMax,2019-06-25,Male,Sphynx\nStella,2021-05-04,Female,American Curl\nRocky,2018-09-20,Male,Burmese\nMolly,2022-01-11,Female,LaPerm\n\nI wrapped this all up in a function that you could add to your page. After adding it, don't forget to actually add the links/button/etc to enable it.\nThere's two issues with my code that come to mind, and I'd love people to comment in with their ideas. First, both the text and Markdown assume the first row is a header. In theory that might not always be accurate. I could update the code to see if the first cell in the first row is th versus td, but that kinda feels like overkill.\nThe second issue that comes to mind is user feedback. Usually these types of things will provide some kind of visual feedback (&quot;Data copied!&quot;). My code does not. I honestly don't quite know how I'd do that. Perhaps a data attribute that would replace the text (assuming it's a link or button) temporarily? I'm not sure.\nIf you've got ideas, leave me a comment below. Here's the demo:\n\n  See the Pen \n  Copy Table As (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	}

]