Commit Graph

297 Commits

Author SHA1 Message Date
jamey
914e0661a1 update url-redirects plan status to complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:32:26 +00:00
jamey
c69e51051f wire simple pages to PageRenderer (stage 3)
Home, Content (about/delivery/privacy/terms), Contact, and ErrorHTML
now render through the generic PageRenderer instead of hardcoded
templates. Block wrapper divs enable CSS grid targeting. Featured
products block supports layout/card_variant/columns settings for
different page contexts. Contact page uses CSS grid on data-block-type
attributes for two-column layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:29:20 +00:00
jamey
ca9f32fa42 update progress and plan for page builder stages 1-2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:08:57 +00:00
jamey
32f54c7afc add generic page renderer with block dispatch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:07:57 +00:00
jamey
35f96e43a6 add page builder data model, cache, and block registry
Stage 1 of the page builder: Pages schema with 14 valid slugs,
BlockTypes registry (26 block types with settings schemas and data
loaders), Defaults module matching existing templates, ETS-backed
PageCache GenServer, and Pages context (get_page/save_page/reset_page
with cache -> DB -> defaults lookup). 34 tests, zero visual change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:45:07 +00:00
jamey
0c54861eb6 add analytics-powered 404 monitoring with FTS5 auto-resolve
All checks were successful
deploy / deploy (push) Successful in 9m48s
BrokenUrlTracker now queries real analytics pageview counts instead of
hardcoding 0, so broken URLs with prior traffic are distinguished from
bot noise. For /products/ 404s with a single FTS5 search match, auto-
creates a redirect and marks the broken URL resolved. 1232 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:08:25 +00:00
jamey
6e57af82fc add URL redirects with ETS-cached plug, broken URL tracking, and admin UI
All checks were successful
deploy / deploy (push) Successful in 3m30s
Redirects context with redirect/broken_url schemas, chain flattening,
ETS cache for fast lookups in the request pipeline. BrokenUrlTracker
plug logs 404s. Auto-redirect on product slug change via upsert_product
hook. Admin redirects page with active/broken tabs, manual create form.
RedirectPrunerWorker cleans up old broken URLs. 1227 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 14:14:14 +00:00
jamey
23e95a3de6 make PDP variant selection work without JS
All checks were successful
deploy / deploy (push) Successful in 1m3s
Variant options (colour, size) are now URL params handled via
handle_params instead of phx-click events. Swatches and size buttons
render as patch links in shop mode, so changing variants works as
plain navigation without JS. Quantity is now a number input that
submits with the form. Unavailable variants render as disabled spans.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:08:36 +00:00
jamey
a61adf4939 prevent search/cart link navigation when JS modal is active
Some checks failed
deploy / deploy (push) Has been cancelled
Without JS the header icons are plain <a> links to /search and /cart.
With LiveView connected, the hooks now preventDefault on those links
so only the modal/drawer opens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:07:51 +00:00
jamey
d0ea9d59f5 add no-JS fallback for cart country selector
All checks were successful
deploy / deploy (push) Successful in 59s
The delivery country form now has action="/cart/country" with a
noscript submit button. Without JS, changing the country and clicking
Update POSTs to a new CartController.update_country action that saves
the country to session and redirects back to /cart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:14:48 +00:00
jamey
79764c7766 add test asserting order tracking form has no-JS action
All checks were successful
deploy / deploy (push) Successful in 36s
Checks the form renders with action='/contact/lookup' and method='post'
so it works when JS is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:11:12 +00:00
jamey
6289c678f7 add no-JS fallback for contact page order tracking form
Some checks failed
deploy / deploy (push) Has been cancelled
Both order tracking forms now have action="/contact/lookup" so they
POST to a new OrderLookupController.lookup action when JS is off.
The controller mirrors the LiveView handler: checks for paid orders,
sends the verification email, and redirects with a flash message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:10:15 +00:00
jamey
6ee8a31716 add test asserting add-to-cart button renders as submit
All checks were successful
deploy / deploy (push) Successful in 48s
Checks the button is type="submit" inside the phx-submit form.
This would have caught the :shop vs :live mode mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:00:02 +00:00
jamey
c0427d6956 fix add-to-cart button not submitting in shop mode
All checks were successful
deploy / deploy (push) Successful in 1m5s
The button type condition checked @mode == :live but ThemeHook sets
mode to :shop, so the button rendered as type="button" (doing nothing).
Changed to @mode == :preview to match the existing phx-click pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:58:41 +00:00
jamey
0b0adba0fe add no-JS progressive enhancement for all shop flows
All checks were successful
deploy / deploy (push) Successful in 1m23s
Every key shop flow now works via plain HTML forms when JS is
unavailable. LiveView progressively enhances when JS connects.

- PDP: form wraps variant/qty/add-to-cart with action="/cart/add"
- Cart page: qty +/- and remove use form POST fallbacks
- Cart/search header icons are now links with phx-click enhancement
- Collection sort form has GET action + noscript submit button
- New /search page with form-based search for no-JS users
- CartController gains add/remove/update_item POST actions
- CartHook gains update_quantity_form and remove_item_form handlers
- Fix flaky analytics tests caused by event table pollution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:56:19 +00:00
jamey
f788108665 add favicon and site icon generation from uploaded images
All checks were successful
deploy / deploy (push) Successful in 1m26s
Upload a source image (PNG, JPEG, or SVG) and get a complete favicon
setup: PNG variants at 32, 180, 192, 512px served from DB via
FaviconController with ETag caching, SVG favicon for vector sources,
dynamic site.webmanifest, and theme-color meta tag. Theme editor gains
a site icon section with "use logo as icon" toggle, dedicated icon
upload, short name, and background colour picker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:22:15 +00:00
jamey
12d87998ee make entire product card image area clickable
All checks were successful
deploy / deploy (push) Successful in 1m18s
The stretched-link::after overlay (z-index: 0) was blocked by
product-card-image-wrap (z-index: 1), so only the title text was
actually clickable. Wrapping the image area in a <.link> component
directly fixes this — taps/clicks bubble up to the link naturally,
and touch-scroll on the image carousel still works on mobile.

Also corrects the mode check: ThemeHook sets mode: :shop on shop pages,
not :live, so the condition is now mode != :preview (consistent with
how the title link already worked).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 15:22:43 +00:00
jamey
e7656cf0b3 fix social link items stacking vertically due to cascade conflict
All checks were successful
deploy / deploy (push) Successful in 1m23s
.themed-button-outline:where(a) { display: inline-block } (line 2147)
was beating .social-link-card-item { display: flex } (line 1796) —
same layer, same specificity, later wins. Used the parent selector
.social-link-card-list .social-link-card-item to give display: flex
higher specificity (2 classes vs 1) so it wins.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:49:13 +00:00
jamey
dffc0eeb44 remove font-size override from social link card items
All checks were successful
deploy / deploy (push) Successful in 1m0s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:42:09 +00:00
jamey
14dc41efcc remove font-size override from card-inline-form button
All checks were successful
deploy / deploy (push) Successful in 1m1s
Follows the same fix as inputs — the --t-text-small override was making
inline form buttons smaller than other buttons on the page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:40:19 +00:00
jamey
c3cc911c5c make themed-input font size consistent across all contexts
Some checks failed
deploy / deploy (push) Has been cancelled
card-inline-form was overriding input font-size to --t-text-small, making
inline inputs noticeably smaller than contact form inputs. Removed the
override so all inputs inherit the same base size from the themed container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:39:26 +00:00
jamey
7121752402 fix inconsistent font size on contact form inputs
All checks were successful
deploy / deploy (push) Successful in 59s
themed-input was inheriting font size from its container rather than
being set explicitly, so contact form inputs appeared larger than
the compact inline inputs in cards like "Track your order".

Added font-size: var(--t-text-base) to the contact form context rule
so both inputs are intentionally sized rather than relying on inheritance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:37:32 +00:00
jamey
cf87c3ff03 add base padding to themed-input and fix contact form selector
All checks were successful
deploy / deploy (push) Successful in 1m0s
themed-input had no padding in the base style, so inputs were rendering
with browser-default 1px/2px padding. Added 0.5rem 0.75rem as the base.

Also fixed the contact form CSS which was targeting .shop-input (a class
that doesn't exist on rendered elements) instead of .themed-input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:35:54 +00:00
jamey
781ebc8cd8 fix privacy policy niggles and add last-updated date to legal pages
All checks were successful
deploy / deploy (push) Successful in 1m19s
- Capitalise lead sentence regardless of shop_name value
- Add stripe.com/privacy URL when mentioning Stripe in payment section
- Remove mention of logout from session cookie description
- Make third-party sharing text provider-agnostic (no longer names Printify etc.)
- Add :updated_at block to privacy, delivery, and terms pages showing when
  content last changed — auto-tracked via content hash, so the date advances
  automatically whenever relevant settings change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:33:01 +00:00
jamey
933f685b63 add legal page generator for privacy, delivery, and terms
All checks were successful
deploy / deploy (push) Successful in 1m23s
Replaces hardcoded PreviewData placeholders with generated content
derived from real shop state: connected providers (production lead
times), shipping countries (grouped by region), shop country
(jurisdiction language and governing law), and feature flags
(abandoned cart recovery section, newsletter, VAT clause).

Returns policy correctly cites Consumer Contracts Regulations Reg
28(1)(b) for POD exemption and Consumer Rights Act for defective goods.
Cart recovery section uses jurisdiction-specific wording: PECR Reg 22
for UK, GDPR Art 6(1)(f) for EU, generic otherwise.

About page unchanged — shop owner's story to tell.

26 new tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 13:48:49 +00:00
jamey
fd355c3397 use shop_name and email_from_address settings for all outbound emails
All checks were successful
deploy / deploy (push) Successful in 1m22s
All three notifier functions (order confirmation, shipping, cart
recovery) now read from the same two settings rather than using
hardcoded values or duplicating the lookup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 13:19:54 +00:00
jamey
61887b9d5b improve cart recovery: product links in email, persistent session cookie
All checks were successful
deploy / deploy (push) Successful in 3m32s
- add product_id to order_items (migration + schema + create_order)
- cart recovery email now includes a direct product link per item
- extend session cookie max_age to 7 days so carts survive browser restarts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 13:12:41 +00:00
jamey
2f4cd81f98 add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).

Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
  signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML

Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design

Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
  has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
jamey
758e66db5c add analytics CSV export
All checks were successful
deploy / deploy (push) Successful in 1m25s
Downloads a ZIP with one CSV per report (overview, trend, pages, entry/exit
pages, sources, referrers, countries, devices, funnel). Export button lives
next to the period selector and picks up the current period and any active
filters using a JS hook + JS.set_attribute, so the downloaded data always
matches what's on screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 09:37:45 +00:00
jamey
01ff8decd5 add order status lookup for customers
All checks were successful
deploy / deploy (push) Successful in 1m17s
Magic link flow on contact page: customer enters email, gets a
time-limited signed link, clicks through to /orders showing all their
paid orders and full detail pages with thumbnails and product links.

- OrderLookupController generates/verifies Phoenix.Token signed links
- Contact LiveView handles lookup_orders + reset_tracking events
- Orders and OrderDetail LiveViews gated by session email
- Order detail shows thumbnails, links to products still available
- .themed-button gets base padding/font-weight so all usages are consistent
- order-summary-card sticky scoped to .cart-grid (was leaking to orders list)
- 27 new tests (1095 total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 08:40:08 +00:00
jamey
4e36b654d3 add JSON-LD structured data
Product pages: Product schema (name, description, image, price/currency,
availability) + BreadcrumbList (Home > Category > Product). Home page:
Organization schema (name, url). Uses Jason with html_safe escaping so
the JSON is safe to embed in <script> tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 22:37:34 +00:00
jamey
0f1135256d add canonical URLs, robots.txt, and sitemap.xml
Canonical: all shop pages now assign og_url (reusing the existing og:url
assign), which the layout renders as <link rel="canonical">. Collection
pages strip the sort param so ?sort=price_asc doesn't create a duplicate
canonical.

robots.txt: dynamic controller disallows /admin/, /api/, /users/,
/webhooks/, /checkout/. Removed robots.txt from static_paths so it
goes through the router instead of Plug.Static.

sitemap.xml: auto-generated from all visible products + categories +
static pages, served as application/xml. 8 tests.

Also updates PROGRESS.md: marks tasks 55, 58, 59, 61, 62 as done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:47:35 +00:00
jamey
b11f7d47d0 add open graph and twitter card meta tags
Product pages get og:type=product, og:url, og:image (hero image), and
twitter:card=summary_large_image. All other shop pages get og:type=website
and twitter:card=summary. og:title and og:description mirror the existing
page title and meta description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:37:50 +00:00
jamey
c6da3b3d2b add meta descriptions to all shop pages
All checks were successful
deploy / deploy (push) Successful in 1m38s
Product pages: first 155 chars of description, stripped of HTML, truncated on word boundary.
Collections: contextual description based on collection type.
Content pages (about, delivery, privacy, terms): from hero_description text.
Contact: static description.
Home falls through to site_description from settings (already in layout).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:31:35 +00:00
jamey
45f05c8bb7 add site name separator to shop page titles
All checks were successful
deploy / deploy (push) Successful in 1m19s
All shop pages now render as "Page · Store Name" using live_title suffix.
Home stays as "Home · Store Name" for consistency with live navigation.
Updated cart test to use regex for whitespace-tolerant title matching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:23:09 +00:00
jamey
9b78793701 add entry/exit pages panel to analytics dashboard
All checks were successful
deploy / deploy (push) Successful in 1m28s
ROW_NUMBER() window function picks first/last pageview per session.
Both tables live in the pages tab and support the pathname filter.
6 new tests, 1061 total.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:14:24 +00:00
jamey
162a5bfe9a replace analytics double-count prevention with buffer supersede
All checks were successful
deploy / deploy (push) Successful in 1m13s
The Plug records a pageview with a known ID (plug_ref) into the ETS
buffer. When JS connects, the LiveView hook supersedes that event by
ID and records its own with full data (screen_size from connect params).
If JS never connects, the Plug's event flushes normally after 10s.

Also fixes: admin browsing no longer leaks product_view events — the
Plug now sets no analytics session data for admins, so all downstream
visitor_hash guards naturally filter them out.

Replaces the previous time-based skip logic which was brittle and
race-prone. The supersede approach is deterministic and handles both
the ETS buffer and already-flushed DB cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:48:50 +00:00
jamey
7ceee9c814 add dashboard filtering to analytics
All checks were successful
deploy / deploy (push) Successful in 1m19s
Click any row in pages, sources, countries, or devices tables to
filter the entire dashboard by that dimension. Active filters show
as dismissible chips. Filters thread through all queries including
previous-period deltas. 1050 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:46:34 +00:00
jamey
6eda1de1bc add period comparison deltas to analytics stat cards
All checks were successful
deploy / deploy (push) Successful in 1m21s
Each stat card now shows the percentage change vs the equivalent
previous period (e.g. 30d compares last 30 days vs 30 days before).
Handles zero-baseline with "new" label and caps extreme deltas at
>999%. Seed data extended to 2 years for meaningful 12m comparisons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:01:25 +00:00
jamey
08fcd60eb6 improve analytics chart with hourly today view and readable labels
- add visitors_by_hour query for hourly breakdown on "today" period
- replace SVG-only chart with HTML/CSS grid layout (bars + labels)
- Y-axis scale with nice rounded max, midpoint, and zero
- X-axis date labels (formatted as "Feb 18") spaced evenly
- adaptive bar gaps (1px for sparse data, 0 for 365-day dense view)
- labels use real HTML text so they're readable on mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:28:35 +00:00
jamey
65e646a7eb add analytics v2 plan, demo seed data, and improved funnel display
- analytics-v2 plan with prioritised improvements (comparison mode, filtering, CSV export, entry/exit pages)
- seed script generating ~35k realistic events over 12 months (weighted traffic, referrers, devices, e-commerce funnel)
- funnel chart now shows overall conversion rate from product views instead of step-to-step percentages
- summary line with overall conversion rate and revenue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:30:24 +00:00
jamey
f91b47f0c3 include browser/os/screen_size in e-commerce analytics events
All checks were successful
deploy / deploy (push) Successful in 1m37s
Event call sites (product_view, add_to_cart, checkout_start, purchase)
were only passing visitor_hash and pathname, leaving browser, OS, screen
size and country nil. Add AnalyticsHook.attrs/1 helper to extract common
analytics fields from socket assigns, and use it in all LiveView event
call sites. Checkout controller reads the same fields from the session.

Also fix plug analytics test to clear stale events before assertions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:13:47 +00:00
jamey
e26a02a0fb fix setup flow stale state and Stripe URL issues
All checks were successful
deploy / deploy (push) Successful in 1m9s
Onboarding: re-fetch setup_status() after provider/Stripe connect instead
of manually patching the local assigns, which could miss admin_created and
leave users stuck on the setup page with no way forward.

Dev config: respect PHX_HOST for endpoint URL so Stripe checkout redirects
to the correct host instead of always using localhost.

Stripe setup: detect private/LAN IPs (10.x, 172.16-31.x, 192.168.x) as
unreachable, not just localhost — prevents creating webhook endpoints that
Stripe can never reach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:51:44 +00:00
jamey
2bd2e613c7 add privacy-first analytics with progressive event collection
All checks were successful
deploy / deploy (push) Successful in 3m20s
Three-layer pipeline: Plug for all HTTP requests (no JS needed), LiveView
hook for SPA navigations, JS hook for screen width. ETS-backed buffer
batches writes to SQLite every 10s. Daily-rotating salt for visitor hashing.
Includes admin dashboard with date ranges, visitor trends, top pages,
sources, devices, and e-commerce conversion funnel. Oban cron for 12-month
data retention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:50:55 +00:00
jamey
b0aed4c1d6 add Printify client test coverage with Req.Test stubs
All checks were successful
deploy / deploy (push) Successful in 1m15s
Same pattern as the Printful work: wire up base_options/0 so tests can
inject a Req.Test plug, fix unreachable 204 clause in delete, add
HTTP-level client tests and provider integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:35:24 +00:00
jamey
a45e85ef4c add Printful client test coverage with Req.Test stubs
All checks were successful
deploy / deploy (push) Successful in 1m10s
Wire up Req.Test plug for the Printful HTTP client so tests can stub
responses. Adds HTTP-level tests for the client, provider integration
tests, and mockup enricher tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:20:49 +00:00
jamey
f1b4e55cc7 mark email settings and setup auto-confirm as done
All checks were successful
deploy / deploy (push) Successful in 53s
Both features were already fully implemented. Updated task list
and plan status to reflect reality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:33:11 +00:00
jamey
34b647dd36 update PROGRESS.md with SQLite tuning work
All checks were successful
deploy / deploy (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:25:59 +00:00
jamey
1989ddb361 update PROGRESS.md with test count
All checks were successful
deploy / deploy (push) Successful in 59s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:24:41 +00:00
jamey
c7555a4bd0 keep one machine always running on Fly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:24:38 +00:00