<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>stafford williams</title>
    <description>bags dev</description>
    <link>https://staffordwilliams.com/dev/</link>
    <pubDate>Sun, 01 Nov 2026 10:13:19 GMT</pubDate>
    <lastBuildDate>Sun, 01 Nov 2026 10:13:19 GMT</lastBuildDate>
    <generator>Astro</generator>
    <item>
      <title>bags: Farfetch pagination loop, CI versioning, log flushing</title>
      <description>&lt;p&gt;Follow-up fixes after the logging release, plus build versioning in CI.&lt;/p&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;CI passes build version into the Docker image&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Farfetch infinite loop on empty pagination&lt;/li&gt;&lt;li&gt;FWRD USD price edge cases&lt;/li&gt;&lt;li&gt;Logs lost on exit&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Mon, 15 Feb 2021 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2021-w07/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2021-w07/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Serilog to Postgres, GitHub Actions CI, FWRD USD prices</title>
      <description>&lt;p&gt;Production readiness: structured logs stored in the database and a CI pipeline on every push.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Structured Serilog logging to PostgreSQL&lt;/li&gt;&lt;li&gt;GitHub Actions build and test pipeline&lt;/li&gt;&lt;li&gt;Version shown in console output&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Parse errors keep more context&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;FWRD prices shown in USD&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sun, 14 Feb 2021 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2021-w06/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2021-w06/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Failed FWRD parses saved for debugging</title>
      <description>&lt;p&gt;A small debugging aid that keeps the HTML of any FWRD product that fails to parse.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Failed FWRD product HTML saved to &lt;code&gt;payload.txt&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Fri, 29 Jan 2021 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2021-w04/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2021-w04/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Edge-pixel image check limited to LegitBags</title>
      <description>&lt;p&gt;The image quality check was too aggressive for some feeds, so it now only runs where clean backgrounds are expected.&lt;/p&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Edge-pixel image check only applies to the LegitBags edit&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sat, 07 Nov 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w45/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w45/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Prada and Michael Kors, split image commands</title>
      <description>&lt;p&gt;Prada and Michael Kors were added, and the image commands were split up so each does one job.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Michael Kors via its JSON API&lt;/li&gt;&lt;li&gt;Bad image blocking&lt;/li&gt;&lt;li&gt;Prada bags with highest-resolution srcset images&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Metadata, Twitter image and S3 upload split into separate commands&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sun, 01 Nov 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w44/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w44/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Four new retailers, edits for multiple feeds</title>
      <description>&lt;p&gt;The new category architecture paid off with four more retailers, and edits let one database drive several themed feeds.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;The Iconic dresses&lt;/li&gt;&lt;li&gt;Edits for multiple curated feeds&lt;/li&gt;&lt;li&gt;Per-edit JSON export&lt;/li&gt;&lt;li&gt;Tory Burch, Ferragamo and Rebecca Minkoff&lt;/li&gt;&lt;li&gt;&lt;code&gt;TestMetadataCommand&lt;/code&gt; for a single product URL&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;More FWRD parsing moved into &lt;code&gt;HtmlHelpers&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Better FWRD dresses handling&lt;/li&gt;&lt;li&gt;Shared &lt;code&gt;HtmlHelpers&lt;/code&gt; for HTML parsing&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Product key collisions across retailers&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Wed, 07 Oct 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w41/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w41/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Polymorphic ProductCategory refactor, TestAll command</title>
      <description>&lt;p&gt;Retailer-specific behaviour moved out of scattered switch statements and into per-retailer category classes.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;TestAllCommand&lt;/code&gt; checks every category&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Category name in the nothing-to-tweet log&lt;/li&gt;&lt;li&gt;&lt;code&gt;Category&lt;/code&gt; enum replaced by per-retailer &lt;code&gt;ProductCategory&lt;/code&gt; classes&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Outnet shoes URL after the clearance path changed&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Fri, 02 Oct 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w40/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w40/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Tweet hashtags and web gallery component</title>
      <description>&lt;p&gt;&amp;lt;h2 id=&amp;quot;v0-12&amp;quot;&amp;gt;&amp;lt;time datetime=&amp;quot;2020-09-21&amp;quot;&amp;gt;Sep 21&amp;lt;/time&amp;gt; Tweet hashtags and web gallery component &amp;lt;span&amp;gt;v0.12&amp;lt;/span&amp;gt;&amp;lt;/h2&amp;gt;&lt;/p&gt;&lt;p&gt;I integrated the hashtag generator into the tweet workflow. Tweets now include hashtags based on the brand name, making them more discoverable on Twitter. I also fixed a bug in the generator where spaces weren&apos;t being stripped properly.&lt;/p&gt;&lt;p&gt;The scraper now extracts product tags from detail pages and stores them in a new database field. I refactored the &lt;code&gt;GetImagesCommand&lt;/code&gt; into a more general &lt;code&gt;GetMetadataCommand&lt;/code&gt; that returns both images and tags as &lt;code&gt;ProductMetadata&lt;/code&gt;. This metadata can be used to add relevant hashtags beyond just the brand name.&lt;/p&gt;&lt;p&gt;I implemented a full reskin of the website view of the feed and most importantly it now works on mobile. To achieve the design I had to override the styling on strikethrough text which is not available natively. The approach I&apos;ve used to have orange strikethrough is &lt;a href=&quot;https://www.tjvantoll.com/2013/09/12/building-custom-text-strikethroughs-with-css/&quot;&gt;detailed here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;When using material-ui/makeStyles in React (JSS?) you can&apos;t set &lt;code&gt;content: &apos;&apos;&lt;/code&gt; as it produces an invalid style. Instead wrap in quotes: &lt;code&gt;content: &amp;quot;&apos;&apos;&amp;quot;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;I built a new &lt;code&gt;Gallery&lt;/code&gt; component with a proper image viewer and placeholder loading states. The gallery uses a &lt;code&gt;flipOnIndex&lt;/code&gt; utility to handle image navigation, which I test-drove with unit tests. The component uses JSX and has been refactored for better separation of concerns with &lt;code&gt;ProductDetail&lt;/code&gt;, &lt;code&gt;ProductImage&lt;/code&gt;, and &lt;code&gt;Placeholder&lt;/code&gt; components.&lt;/p&gt;</description>
      <pubDate>Mon, 21 Sep 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w39/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w39/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Brand hashtags, xUnit test project, ScrapeUrl debugging</title>
      <description>&lt;p&gt;Tweets gained brand hashtags, and the project picked up its first tests and a page-source debugging tool.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;ScrapeUrlCommand&lt;/code&gt; logs a page&apos;s source for debugging&lt;/li&gt;&lt;li&gt;Brand hashtag generator&lt;/li&gt;&lt;li&gt;xUnit and Shouldly test project&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Dropped the bundled ChromeDriver package&lt;/li&gt;&lt;li&gt;Solution file for building and testing together&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sat, 19 Sep 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w38/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w38/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Responsive product grid replaces fullpage scrolling</title>
      <description>&lt;p&gt;The web frontend swapped one-product-per-screen scrolling for a grid that&apos;s much faster to browse.&lt;/p&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Responsive 2/3/4-column grid replaces fullpage scrolling&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sat, 05 Sep 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w36/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w36/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Farfetch handbags and shoes, embedded JSON parsing, Outnet heels</title>
      <description>&lt;p&gt;Farfetch made four retailers, and scraping moved towards parsing the JSON sites embed in their pages.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Outnet heels category&lt;/li&gt;&lt;li&gt;Farfetch handbags and shoes&lt;/li&gt;&lt;li&gt;&lt;code&gt;GetImageSourcesFromPage&lt;/code&gt; for multi-image detail pages&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Category passed into &lt;code&gt;ToEntity&lt;/code&gt; and image logic&lt;/li&gt;&lt;li&gt;Outnet parsed from embedded page JSON&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Duplicate products across category listings&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Tue, 18 Aug 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w34/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w34/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: The Outnet coats, pagination, AngleSharp over Selenium</title>
      <description>&lt;p&gt;The Outnet joined as a third retailer, and a timing comparison settled on AngleSharp for parsing it.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Outnet scraper pagination&lt;/li&gt;&lt;li&gt;The Outnet coats as a third retailer&lt;/li&gt;&lt;li&gt;Headless Chrome option&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Outnet parsing switched to AngleSharp&lt;/li&gt;&lt;li&gt;Timed Selenium vs AngleSharp extraction comparison&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Thu, 13 Aug 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w33/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w33/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Paginated JSON export, React frontend, LocalStack</title>
      <description>&lt;p&gt;Tweeted products got a home on the web: a JSON export feeding a static React site served from S3.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Fullpage scrolling product display with image galleries&lt;/li&gt;&lt;li&gt;Price, savings and retailer link on each product&lt;/li&gt;&lt;li&gt;Frontend S3 bucket config from &lt;code&gt;config.json&lt;/code&gt;&lt;/li&gt;&lt;li&gt;React web frontend hosted on S3&lt;/li&gt;&lt;li&gt;Combined &lt;code&gt;ScrapeAndTweet&lt;/code&gt; command&lt;/li&gt;&lt;li&gt;LocalStack docker-compose for local S3&lt;/li&gt;&lt;li&gt;Paginated JSON export of products&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Export uploads JSON straight to S3&lt;/li&gt;&lt;li&gt;Reusable &lt;code&gt;S3UploadCommand&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Twitter image sizing across categories&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Thu, 06 Aug 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w32/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w32/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: MediatR commands, S3 image storage, brand split</title>
      <description>&lt;p&gt;A big architectural overhaul onto MediatR commands, with processed images now stored in S3 instead of on local disk.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Processed images uploaded to S3 by date&lt;/li&gt;&lt;li&gt;Backfill command for images of existing products&lt;/li&gt;&lt;li&gt;Brand stored separately from product name&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;MediatR command/handler architecture&lt;/li&gt;&lt;li&gt;Snake_case database columns&lt;/li&gt;&lt;li&gt;Dapper for raw SQL in content generation&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sun, 02 Aug 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w31/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w31/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: FWRD shoes, dresses and bags, multi-image tweets</title>
      <description>&lt;p&gt;The scraper grew from one retailer to four FWRD and Coach feeds, with each feed tweeting from its own pool and richer multi-image posts.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;FWRD dresses and bags categories&lt;/li&gt;&lt;li&gt;Up to four images per tweet&lt;/li&gt;&lt;li&gt;Price filter excluding products over $1000&lt;/li&gt;&lt;li&gt;Category selectable via environment variable&lt;/li&gt;&lt;li&gt;FWRD shoes as a second retailer&lt;/li&gt;&lt;li&gt;Category system with per-feed product selection&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Logging through &lt;code&gt;ILogger&lt;/code&gt; instead of the console&lt;/li&gt;&lt;li&gt;Category-specific image sizing in a dedicated &lt;code&gt;ImageProcessor&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Database operations moved into &lt;code&gt;DatabaseContext&lt;/code&gt;&lt;/li&gt;&lt;li&gt;FWRD parsed from its lazy-load API with AngleSharp&lt;/li&gt;&lt;li&gt;Variable-width images keep their aspect ratio&lt;/li&gt;&lt;li&gt;Retailer scraping split into page object models&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Crash on FWRD products without a sale price&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sat, 25 Jul 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w30/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w30/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Postgres persistence, Twitter posting, card image processing</title>
      <description>&lt;p&gt;Scraped products now land in a database and get tweeted automatically, with images reshaped to suit Twitter cards.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Product images fitted to Twitter card size with ImageSharp&lt;/li&gt;&lt;li&gt;Tweets a sale product with image, discount and prices&lt;/li&gt;&lt;li&gt;PostgreSQL product persistence via EF Core&lt;/li&gt;&lt;li&gt;Random unposted product selection with image download&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Changed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Generic host with DI and user secrets config&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Fixed&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Posted status not saved after tweeting&lt;/li&gt;&lt;li&gt;Crash when there are no new products to tweet&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Sat, 18 Jul 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w29/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w29/</guid>
      <category>bags</category>
    </item>
    <item>
      <title>bags: Coach bags Selenium scraper, Docker container</title>
      <description>&lt;p&gt;The project started as a C# console app scraping Coach&apos;s sale page with Selenium, packaged to run headless in Docker.&lt;/p&gt;&lt;h3&gt;Added&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Selenium scraper for the Coach Australia bags sale&lt;/li&gt;&lt;li&gt;Anti-detection Chrome setup&lt;/li&gt;&lt;li&gt;Dockerfile for headless runs&lt;/li&gt;&lt;/ul&gt;</description>
      <pubDate>Mon, 06 Jul 2020 00:00:00 GMT</pubDate>
      <link>https://staffordwilliams.com/dev/bags/2020-w28/</link>
      <guid isPermaLink="true">https://staffordwilliams.com/dev/bags/2020-w28/</guid>
      <category>bags</category>
    </item>
  </channel>
</rss>
