Commit Graph

134 Commits

Author SHA1 Message Date
Deeman
831233cb29 fix(admin): add missing article_stats route, 500 handler, dev debug mode
- Add /admin/articles/stats HTMX partial endpoint that was referenced
  by article_stats.html but never created (caused 500 during generation)
- Add @app.errorhandler(500) to log exceptions with traceback
- Switch dev_run.sh from Granian to Quart debug mode for browser
  tracebacks and auto-reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:34:22 +01:00
Deeman
c5327c4012 fix(maps): move VENUE_ICON creation after Leaflet loads
L.divIcon() was called at IIFE top level before the dynamic Leaflet
script loaded, throwing ReferenceError and preventing all maps from
rendering. Move icon creation into script.onload callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:01:54 +01:00
Deeman
93c9408f6b fix(admin): render Leaflet maps in template preview
The .card wrapper has overflow:hidden which clips Leaflet's
absolutely-positioned tile layers. Override to overflow:visible
on the rendered-article card. Add .catch() to map fetch calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 22:58:27 +01:00
Deeman
aa27f14f3c fix(pipeline): eurostat filter bugs + supervisor uses sqlmesh plan
- nrg_pc_203: add missing unit=KWH filter (API returns 2 units)
- lc_lci_lev: fix currency→unit filter dimension name
- supervisor: use `sqlmesh plan prod --auto-apply` instead of
  `sqlmesh run` so new/changed models are detected automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 17:19:12 +01:00
Deeman
c3f15535b8 fix(pipeline): handle DuckDB catalog naming in diagnostic script
The lakehouse.duckdb file uses catalog "lakehouse" not "local", causing
SQLMesh logical views to break. Script now auto-detects the catalog via
USE and falls back to physical tables when views fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 17:06:44 +01:00
Deeman
6b7fa45bce feat(admin): add pipeline diagnostic script + extraction card UX improvements
- Add scripts/check_pipeline.py: read-only diagnostic for pricing pipeline
  row counts, date range analysis, HAVING filter impact, join coverage
- Add description field to all 12 workflows in workflows.toml
- Parse and display descriptions on extraction status cards
- Show spinner + "Running" state with blue-tinted card border
- Display start time with "running..." text for active extractions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:40:12 +01:00
Deeman
59f1f0d699 merge(worktree): interactive maps for market pages
Self-hosted Leaflet 1.9.4 maps across 4 placements: markets hub
country bubbles, country overview city bubbles, city venue dots, and
a standalone opportunity map. New /api blueprint with 4 JSON endpoints.
New city_venue_locations SQLMesh serving model. No CDN — GDPR-safe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

# Conflicts:
#	CHANGELOG.md
2026-03-04 15:36:41 +01:00
Deeman
0a89ba2213 docs: update CHANGELOG with interactive maps feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:33:35 +01:00
Deeman
61c197d233 merge(worktree): individualise article costs with per-country Eurostat data + tiered proxy tenant work
# Conflicts:
#	CHANGELOG.md
#	transform/sqlmesh_padelnomics/models/foundation/dim_cities.sql
#	transform/sqlmesh_padelnomics/models/foundation/dim_locations.sql
2026-03-04 12:44:56 +01:00
Deeman
2e68cfbe4f feat(transform): individualise article costs with per-country Eurostat data
Add real per-country cost data to ~30 calculator fields so pSEO articles
show country-specific CAPEX/OPEX instead of hardcoded DE defaults.

Extractor:
- eurostat.py: add 8 new datasets (nrg_pc_205, nrg_pc_203, lc_lci_lev,
  5×prc_ppp_ind variants); add optional `dataset_code` field so multiple
  dict entries can share one Eurostat API endpoint

Staging (4 new models):
- stg_electricity_prices — EUR/kWh by country, semi-annual
- stg_gas_prices         — EUR/GJ by country, semi-annual
- stg_labour_costs       — EUR/hour by country, annual (future staffed scenario)
- stg_price_levels       — PLI indices (EU27=100) for 5 categories, annual

Foundation:
- dim_countries (new) — conformed country dimension; eliminates ~50-line CASE
  blocks duplicated in dim_cities/dim_locations; computes ~29 calculator cost
  override columns from PLI ratios and energy price ratios vs DE baseline;
  NULL for DE so calculator falls through to DEFAULTS unchanged
- dim_cities — replace country_name/slug CASE blocks + country_income CTE
  with JOIN dim_countries
- dim_locations — same refactor as dim_cities

Serving:
- pseo_city_costs_de — JOIN dim_countries; add 29 camelCase override columns
  auto-applied by calculator (electricity, heating, rentSqm, hallCostSqm, …)
- planner_defaults — JOIN dim_countries; same 29 cost columns flow through
  to /api/market-data endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 10:09:48 +01:00
Deeman
53fdbd9fd5 docs: update CHANGELOG with bulk actions feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:55:04 +01:00
Deeman
10af6a284c fix(content): slug transliteration, article links, country overview ranking
Some checks failed
CI / test (push) Failing after 30s
CI / tag (push) Has been skipped
# Conflicts:
#	CHANGELOG.md
2026-03-03 16:29:41 +01:00
Deeman
68f354ac2b docs: update CHANGELOG for slug fix + country overview ranking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:26:55 +01:00
Deeman
80c2f111d2 feat(billing): B4-B5 — tests, lint fixes, CHANGELOG + PROJECT.md
- Fix unused imports in stripe.py (hashlib, hmac, time)
- Update test_billing_routes.py: insert into payment_products table,
  fix mock paths for extracted paddle.py, add Stripe webhook 404 test
- Update CHANGELOG.md with Stripe provider feature
- Update PROJECT.md Done section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:07:30 +01:00
Deeman
3ddb26ae0f chore: update CHANGELOG.md and PROJECT.md for CRO overhaul
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 06:44:19 +01:00
Deeman
48401bd2af feat(articles): rewrite B2B article CTAs — directory → /quote form
All checks were successful
CI / test (push) Successful in 50s
CI / tag (push) Successful in 3s
All 12 hall-building articles now link to /quote (leads.quote_request).
Previously: 2 had broken directory prose, 4 had unlinked planner mentions,
4 had broken [→ placeholder] links, 2 had scenario cards but no CTA link.

- Group 1 (bauen/build-guide): replace directory section with quote CTA
- Group 2 (kosten/risiken): link planner refs, append quote CTA
- Group 3 (finanzierung): append quote CTA after scenario card
- Group 4 (standort/businessplan): fix broken [→] links to /de|en/planner,
  append quote CTA

CTA copy is contextual per article. Light-blue banner pattern, .btn class.
B2C gear articles unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 14:55:28 +01:00
Deeman
cd02726d4c chore(changelog): document B2B article CTA rewrite
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 14:55:20 +01:00
Deeman
34f8e45204 merge(articles): iframe preview + collapsible meta + word count 2026-03-02 12:09:04 +01:00
Deeman
6b9187f420 fix(articles): iframe preview + collapsible meta + word count
Replace the auto-escaped `{{ body_html }}` div (showed raw HTML tags)
with a sandboxed `<iframe srcdoc>` pattern matching the email preview.
Both the initial page load and the HTMX live-update endpoint now build
a full `preview_doc` document embedding the public CSS and wrapping
content in `<div class="article-body">` — pixel-perfect against the
live article, admin styles fully isolated.

Also:
- Delete ~65 lines of redundant `.preview-body` custom CSS
- Add "Meta ▾" toolbar toggle to collapse/expand metadata strip
- Add word count footer in the editor pane (updates on input)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:01:16 +01:00
Deeman
2a7eed1576 merge: test suite compression pass (-197 lines)
All checks were successful
CI / test (push) Successful in 51s
CI / tag (push) Successful in 3s
2026-03-02 10:46:01 +01:00
Deeman
162e633c62 refactor(tests): compress admin_client + mock_send_email into conftest
Lift admin_client fixture from 7 duplicate definitions into conftest.py.
Add mock_send_email fixture, replacing 60 inline patch() blocks across
test_emails.py, test_waitlist.py, and test_businessplan.py. Net -197 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:40:52 +01:00
Deeman
31017457a6 merge: semantic-compression — add compression helpers, macros, and coding philosophy
All checks were successful
CI / test (push) Successful in 50s
CI / tag (push) Successful in 3s
Applies Casey Muratori's semantic compression across all three packages:
- count_where() helper: 30+ COUNT(*) call sites compressed
- _forward_lead(): deduplicates lead forward routes
- 5 SQLMesh macros for country code patterns (7 models)
- skip_if_current() + write_jsonl_atomic() extract helpers
Net: -118 lines (272 added, 390 removed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:00:15 +01:00
Deeman
f93e4fd0d1 chore(changelog): document semantic compression pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:54:44 +01:00
Deeman
b32b7cd748 merge: unify confirm dialog — pure hx-confirm + form[method=dialog]
Eliminates confirmAction() entirely. One code path: all confirmations
go through showConfirm() called by the htmx:confirm interceptor.
14 template files converted to hx-boost + hx-confirm pattern.
Pipeline endpoints updated to exclude HX-Boosted requests from the
HTMX partial path.

# Conflicts:
#	web/src/padelnomics/admin/templates/admin/affiliate_form.html
#	web/src/padelnomics/admin/templates/admin/affiliate_program_form.html
#	web/src/padelnomics/admin/templates/admin/base_admin.html
#	web/src/padelnomics/admin/templates/admin/partials/affiliate_program_results.html
#	web/src/padelnomics/admin/templates/admin/partials/affiliate_row.html
2026-03-02 07:48:49 +01:00
Deeman
d3626193c5 refactor(admin): unify confirm dialog — pure hx-confirm + form[method=dialog]
Eliminate `confirmAction()` and the duplicate `cloneNode` hack entirely.
One code path: everything goes through `showConfirm()` called by the
`htmx:confirm` interceptor.

Dialog HTML:
- `<form method="dialog">` for native close semantics; button `value`
  becomes `dialog.returnValue` — no manual event listener reassignment.

JS:
- `showConfirm(message)` — Promise-based, listens for `close` once.
- `htmx:confirm` handler calls `showConfirm()` and calls `issueRequest`
  if confirmed. Replaces both the old HTMX handler and `confirmAction()`.

Templates (Padelnomics, 14 files):
- All `onclick=confirmAction(...)` and `onclick=confirm()` removed.
- Form-submit buttons: added `hx-boost="true"` to form + `hx-confirm`
  on the submit button.
- Pure HTMX buttons (pipeline_transform, pipeline_overview): `hx-confirm`
  replaces `onclick=if(!confirm(...))return false;`.

Pipeline routes (pipeline_trigger_extract, pipeline_trigger_transform):
- `is_htmx` now excludes `HX-Boosted: true` requests — boosted form
  POSTs get the normal redirect instead of the inline partial.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 07:35:32 +01:00
Deeman
7ea1f234e8 chore(changelog): document htmx:confirm guard fix
All checks were successful
CI / test (push) Successful in 51s
CI / tag (push) Successful in 2s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 22:40:07 +01:00
Deeman
9608b7f601 feat(admin): replace all native confirm() with styled dialog + fix pipeline tabs scrollbar
Some checks failed
CI / tag (push) Has been cancelled
CI / test (push) Has been cancelled
- Add global htmx:confirm handler in base_admin.html that intercepts
  hx-confirm attributes and shows #confirm-dialog instead of window.confirm()
- Convert 4 pipeline HTMX buttons (Run Transform, Run Export, Run Full
  Pipeline, Run extractor) from onclick+confirm() to hx-confirm
- Convert 4 affiliate form/list delete buttons from onclick+confirm()
  to confirmAction() via event.preventDefault()
- Add scrollbar-width:none + ::-webkit-scrollbar{display:none} to
  .pipeline-tabs to suppress spurious horizontal scrollbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 19:47:34 +01:00
Deeman
0d903ec926 chore(changelog): document stale-tier circuit breaker fix
All checks were successful
CI / test (push) Successful in 51s
CI / tag (push) Successful in 3s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:43:18 +01:00
Deeman
1c0edff3e5 chore(changelog): document visual upgrades for longform articles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:29:21 +01:00
Deeman
08bd2b2989 chore(changelog): document proxy URL scheme validation fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:26:57 +01:00
Deeman
a3dd37b1be chore(changelog): document pipeline transform tab + live status feature
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 13:47:07 +01:00
Deeman
a898a06575 feat(proxy): per-proxy dead tracking in tiered cycler
Add proxy_failure_limit param to make_tiered_cycler (default 3).
Individual proxies hitting the limit are marked dead and permanently
skipped. next_proxy() auto-escalates when all proxies in the active
tier are dead. Both mechanisms coexist: per-proxy dead tracking removes
broken individuals; tier-level threshold catches systemic failure.

- proxy.py: dead_proxies set + proxy_failure_counts dict in state;
  next_proxy skips dead proxies with bounded loop; record_failure/
  record_success accept optional proxy_url; dead_proxy_count() added
- playtomic_tenants.py: pass proxy_url to record_success/record_failure
- playtomic_availability.py: _worker returns (proxy_url, result);
  serial loops in extract + extract_recheck capture proxy_url
- test_supervisor.py: 11 new tests in TestTieredCyclerDeadProxyTracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:28:54 +01:00
Deeman
8f2ffd432b fix(admin): correct docker volume mount + pipeline_routes repo root
All checks were successful
CI / test (push) Successful in 50s
CI / tag (push) Successful in 2s
- docker-compose.prod.yml: fix volume mount for all 6 web containers
  from /opt/padelnomics/data (stale) → /data/padelnomics (live supervisor output);
  add LANDING_DIR=/app/data/pipeline/landing so extraction/landing stats work
- pipeline_routes.py: fix _REPO_ROOT parents[5] → parents[4] so workflows.toml
  is found in dev and pipeline overview shows workflow schedules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 11:41:29 +01:00
Deeman
ec839478c3 feat(affiliate): add program + URL assembly tests; update CHANGELOG + PROJECT.md
41 tests total (+15). New coverage: get_all_programs(), get_program(),
get_program_by_slug(), build_affiliate_url() (program path, legacy fallback,
no program_id, no program dict), program-based redirect, legacy redirect,
migration seed assertion, ASIN backfill assertion. All ruff checks pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:51:26 +01:00
Deeman
5c22ea9780 feat(affiliate): tests, ruff cleanup, CHANGELOG + PROJECT.md (commit 9/9)
- 26 tests in web/tests/test_affiliate.py covering hash_ip determinism,
  daily rotation, product CRUD, bake_product_cards marker replacement,
  click redirect (302 + logged), inactive/unknown 404, multi-retailer
- ruff: fix E741 ambiguous var (l → line in _form_to_product), F401 unused
  import, I001 import sort in admin/routes.py
- CHANGELOG: affiliate product system entry
- PROJECT.md: affiliate system moved to Done, Wirecutter backlog item removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:06:01 +01:00
Deeman
adf22924f6 feat(extract): three-tier proxy system with Webshare auto-fetch
Replace two-tier proxy setup (PROXY_URLS / PROXY_URLS_FALLBACK) with
N-tier escalation: free → datacenter → residential.

- proxy.py: fetch_webshare_proxies() auto-fetches the Webshare download
  API on each run (no more stale manually-copied lists). load_proxy_tiers()
  assembles tiers from WEBSHARE_DOWNLOAD_URL, PROXY_URLS_DATACENTER,
  PROXY_URLS_RESIDENTIAL. make_tiered_cycler() generalised to list[list[str]]
  with N-level escalation; is_fallback_active() replaced by is_exhausted().
  Old load_proxy_urls() / load_fallback_proxy_urls() deleted.

- playtomic_availability.py: both extract() and extract_recheck() use
  load_proxy_tiers() + generalised cycler. _fetch_venues_parallel fallback_urls
  param removed. All is_fallback_active() checks → is_exhausted().

- playtomic_tenants.py: flattens tiers for simple round-robin.

- test_supervisor.py: TestLoadProxyUrls removed (function deleted).
  Added TestFetchWebshareProxies, TestLoadProxyTiers, TestTieredCyclerNTier
  (11 tests covering parse format, error handling, escalation, thread safety).

47 tests pass, ruff clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 16:57:07 +01:00
Deeman
120f974970 merge: Phase 2a + 2b — EU NUTS-2 spatial join + US state income
Phase 2a: NUTS-1 regional income for Germany (16 Bundesländer via admin1→NUTS-1 mapping)
Phase 2b: EU-wide NUTS-2 via GISCO spatial join + US Census ACS state income
- All EU-27+EFTA+UK locations now auto-resolve to NUTS-2 via ST_Contains
- Germany gets sub-Bundesland (38 Regierungsbezirke) differentiation
- US gets state-level income with PPS normalisation
- Income cascade: NUTS-2 → NUTS-1 → US state → country-level

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:11:36 +01:00
Deeman
c3531bd75d feat(data): Phase 2b complete — EU NUTS-2 spatial join + US state income
- stg_regional_income: expanded NUTS-1+2 (LENGTH IN 3,4), nuts_code rename, nuts_level
- stg_nuts2_boundaries: new — ST_Read GISCO GeoJSON, bbox columns for spatial pre-filter
- stg_income_usa: new — Census ACS state-level income staging model
- dim_locations: spatial join replaces admin1_to_nuts1 VALUES CTE; us_income CTE with
  PPS normalisation (income/80610×30000); income cascade: NUTS-2→NUTS-1→US state→country
- init_landing_seeds: compress=False for ST_Read files; gisco GeoJSON + census income seeds
- CHANGELOG + PROJECT.md updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:03:16 +01:00
Deeman
5e5a7c1bae docs: CHANGELOG + PROJECT.md for Phase 2a NUTS-1 regional income
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 10:26:48 +01:00
Deeman
5fa8a98903 merge: opportunity score data quality improvements
Phase 0 — income ceiling fix (opportunity_score):
  PPS normalisation /200→/35000; economic power now differentiates
  countries (DE 13.2, ES 10.7, SE 14.3 pts; was 20.0 everywhere)

Phase 1b — overpass_tennis in workflows.toml:
  Monthly schedule added; was only in combined extractor

Phase 2b — dim_cities spatial population fallback:
  GeoNames spatial CTE (ST_Distance_Sphere, 0.14° bbox) resolves
  localization mismatches: Wien→1.69M, Milano→1.37M, München→1.49M
  Coverage: 70.5% → 98.5% (5,401/5,481 cities with population)
2026-02-27 08:52:35 +01:00
Deeman
e32f7ba4b8 docs: CHANGELOG + PROJECT.md for opportunity score data quality improvements
Documents Phase 0 (income ceiling fix), Phase 1b (overpass_tennis workflow),
and Phase 2b (dim_cities spatial population fallback, 70.5%→98.5% coverage).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:48:16 +01:00
Deeman
ce2171614b docs: CHANGELOG + PROJECT.md for group_key grouping + report PDF
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:56:34 +01:00
Deeman
721b2a37df docs: CHANGELOG + PROJECT.md for score recalibration (market_score v3 + opportunity_score v2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 06:58:48 +01:00
Deeman
0b3e1235fa docs: CHANGELOG + PROJECT.md for opportunity_score integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 20:44:07 +01:00
Deeman
a98903646d merge: pricing-overhaul — Basic free, card color €59, BP PDF €149, supplier page CRO, lead-back guarantee 2026-02-26 15:49:57 +01:00
Deeman
9dd0f30014 docs: pricing overhaul + lead-back guarantee — CHANGELOG + PROJECT.md
CHANGELOG: add Added section (guarantee, CRO restructure, credits-only callout,
ROI line) and Changed section (Basic free, card color €59, BP PDF €149,
hero CTA, comparison table, EN/DE translations, setup_paddle.py)

PROJECT.md:
- Correct Done section prices (Growth €199, Pro €499)
- Add Done entries: pricing overhaul, lead-back guarantee
- Add 3 Decisions Log entries (Basic free, guarantee credit-only, static ROI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 15:24:06 +01:00
Deeman
ad1da5c335 feat: outreach follow-up scheduling, activity timeline, and pSEO noindex (migration 0025)
Feature A — Outreach follow-up + activity timeline:
- follow_up_at column on suppliers (migration 0025)
- HTMX date picker on outreach rows, POST /admin/outreach/<id>/follow-up
- Amber due-today banner on /admin/outreach with ?follow_up=due filter
- get_follow_up_due_count() for dashboard widget
- Activity timeline on /admin/suppliers/<id>: merges sent + received emails by contact_email

Feature B — pSEO article noindex:
- noindex column on articles (migration 0025)
- NOINDEX_THRESHOLDS per-template lambdas in content/__init__.py
- generate_articles() evaluates threshold and stores noindex=1 for thin-data articles
- <meta name="robots" content="noindex, follow"> in article_detail.html
- Sitemap excludes noindex articles (AND noindex = 0)
- pSEO dashboard noindex count card + article row badge

Tests: 49 new tests (29 outreach, 20 noindex), 1377 total, 0 failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 17:51:38 +01:00
Deeman
3b248871c2 docs: update CHANGELOG and PROJECT.md for follow-up scheduling, activity timeline, and noindex features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 16:12:56 +01:00
Deeman
cee2e9babc merge: standardise recheck availability to JSONL + update docs 2026-02-25 15:45:23 +01:00
Deeman
606d6f7d4c merge(outreach): admin outreach pipeline + separate sending domain
# Conflicts:
#	CHANGELOG.md
2026-02-25 15:29:20 +01:00