Add the "Executed: 2026-06-06" record to the 2c runbook (new box
gitea-ci-fsn1-1, Falkenstein CX22, IPs, outcome) and fold the real-world
lessons into the steps: pin the Gitea image version (not latest),
ON_ERROR_STOP + count check on DB restore, the old-runner-survives-in-
migrated-DB gotcha (delete from action_runner + stop prod service), generate
runner token as the git user, expected volume-already-exists warning, and the
root-vs-sudo note.
Held local (not pushed) — pushing stacks a 2nd ~3h CI run behind the in-flight one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document two ways to take CI/Gitea load off the production box, since the
HostHighCpuUsage floods are caused by act_runner running ruff/pytest/validate
on the prod server (not by Gitea hosting, which is light):
- 2a "Offloading CI to a Separate Server" — move just the act_runner to a
cheap x86 box (no data migration, no DNS, no downtime). Includes the smaller
build-burst caveat (deploy still builds on prod) + the registry-pull path.
- 2c "Migrating Gitea to a Separate Server" — full separation runbook:
pg_dump + data-volume tar/restore, DNS cutover, Caddy/SSL, rollback. Notes
the box becomes stateful/critical (backups + hardening).
mkdocs --strict clean; arch validation 0 new findings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deploy.sh already pruned old images but never build cache — the larger half
of disk creep from CI rebuilds (root fs hit 83% on prod). Add
`docker builder prune --filter until=168h` alongside the existing image prune
so cleanup happens every deploy, version-controlled, no host cron.
Docs (hetzner-server-setup.md, Maintenance section):
- New "Rescaling / Upgrading the Server" — when/why, same-arch (Arm/CAX) +
CPU-RAM-only vs irreversible disk-expand constraints, poweroff→rescale→
power-on→verify steps, and the Arm-capacity-unavailable-in-DC caveat.
- New "Disk Maintenance (Docker Pruning)" — emergency manual prune + the
automated deploy.sh approach.
- Fixed stale Resource Budget: cadvisor 128→192 MB (matches compose),
total 672→736 MB, and "live-upgrade" wording (rescale needs a power-off).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewed commit 4423f0a5 (generalize 401 redirect to all 4 personas) and
verified production has no coverage gap: the only uncovered storefront page
(/loyalty/join) hits exclusively public, no-auth endpoints that can't 401,
and authenticated storefront pages live under /account/ which is already
covered. Remaining gap is dev/path-based-mode only (/platforms/...) — captured
as a deferred enhancement in docs/proposals/auth-redirect-dev-prod-symmetry.md
with the proposed one-line prefix-strip fix and two open implementation
details. No code shipped this session. mkdocs --strict clean; architecture
validation passed (0 new findings).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picked up the morning's carry-over and ran the full prod-readiness
chain end-to-end. Resolution: SG credential out of git permanently
via untrack + .example template (e44f5c04); per-host migration on
prod (alertmanager.yml gitignored, real file lives outside git);
deploy-api-only.sh succeeded for the first time; today's 9 queued
loyalty commits live on prod with ?v=e44f5c04 (and verified by
re-running the loyalty redirect flicker repro — clean).
Multi-hour rabbit hole on actual email delivery: provider's port 587
PLAIN backend is OAuth-wired (returns RFC 6749 invalid_grant text
for password auth); switched to provider's documented port 465 SSL/TLS
endpoint. Discovered Hetzner Cloud blocks outbound 25 and 465 by
default as anti-spam policy. Auto-approved unblock ticket landed in
minutes; one-line smarthost change to :465 reactivated email
alerting after 13+ days down. alertmanager handles implicit TLS on
465 natively, no stunnel/relay needed.
Hetzner doc updated with the egress-block warning + mail1 SMTP
callout in 1227567d as 5h-debug payback. Next session resumes at
Test 5.2 (/account/loyalty with 168 pts customer) → 5.3 history.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hetzner Cloud silently blocks outbound TCP 25 and 465 on every Cloud
Server. The block sits upstream of the VM — UFW and iptables look
completely clean — so it presents as a generic "connection times out"
that's easy to misdiagnose as a credential or DNS issue. Spent ~5 hours
on 2026-05-30 working through swaks/tcpdump/auth-backend hypotheses
before finding Hetzner's own docs that mention the policy.
Two doc additions:
- Step 4 (Firewall Configuration) gets a warning admonition right after
the UFW status check. Explains the upstream nature of the block,
gives the symptom signature (nc to 587 succeeds, nc to 465 silently
times out), and includes the auto-approved unblock ticket template
with sample text.
- Step 19.5 (Alertmanager SMTP) gets a "live prod uses
mail1.myservices.hosting:465" callout reflecting the reality that
the SendGrid setup documented in that section is no longer how this
prod env is wired. The callout captures the actual smarthost config
(with smtp_auth_password kept gitignored, only .example ships in
repo), the two prerequisites (Hetzner unblock + implicit-TLS-aware
smarthost port), and the redacted swaks verification command. The
rest of §19.5 stays as a reference for greenfield deploys that
prefer SendGrid.
Saves the next person from repeating the same hours-long detour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nine code commits shipped today (5f359283 → c13e8e29) covering Test 5
widget/customer-module i18n, a 53-template cache-bust sweep with
FE-024 rule tightening, the customer-storefront 401-to-/account/login
redirect, the loyalty redirect-flicker fix, the login JS i18n sweep,
and a new scripts/deploy-api-only.sh script + Hetzner §16.5 split.
None of them are on prod yet — surfaced during the deploy that the
new dirty-tree gate is correctly blocking on monitoring/alertmanager/
alertmanager.yml, which holds a SendGrid API key pasted into a tracked
file. Knock-on finding: alertmanager has been running with stale empty
SMTP config for 13+ days, AND the file still references SendGrid
instead of the post-migration smarthost, so prod's alerting is silently
broken. User opted to fix prod-readiness items first thing tomorrow
before resuming Test 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual deploys had been using a bare `git pull && docker compose up -d
--build api` sequence, which works for the container itself but silently
skipped writing `.build-info`. The stale `.build-info` left
`?v=<commit-sha>` pointing at the previous deploy's SHA on every shared
JS/CSS URL — so browsers happily kept cached pre-fix assets even after
a successful rebuild. Bit us today: ~5 hours of "is this even deployed?"
debugging on the loyalty-dashboard redirect-flicker fix.
deploy.sh wasn't a substitute because it's a CI/CD script: stashes
working tree, runs alembic, restarts every service in the full profile
(db, redis, api, celery-worker, celery-beat, flower), 60s health budget.
Heavy and disruptive for an api-only hotfix.
New scripts/deploy-api-only.sh fills the gap with the narrow path:
- Refuses if working tree is dirty (no silent stash → no pop conflicts).
- git pull --ff-only.
- Writes .build-info (the critical missing step).
- docker compose -f docker-compose.yml --profile full up -d --build api
(only the api service — db/redis/celery untouched).
- Tight 30s health budget since DB doesn't need to come back up.
- Exit codes 0/1/2/3 for clean automation.
docs/deployment/hetzner-server-setup.md §16.5 split into 16.5a
(code-only — points at the new script as the default) and 16.5b
(full deploy fallback — kept the existing deploy.sh path for migrations
/ Dockerfile / docker-compose / requirements changes). §12 footnote on
.build-info refreshed to mention both scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five-issue triage shipped as four commits today: storefront i18n sweep
(10a99f98), FR password_reset accents + store-name signature
(b463c6bf), DE password_reset umlauts (36fd3781), Alpine x-text quoting
fix (1bade6e6), plus a seed-script sys.path fix (213a6053) hit during
the prod reseed. Test 5.0 (forgot-password end-to-end on FR) verified
end-of-day; Test 5 proper (login + dashboard + history) blocks on
recreating the prod api container tomorrow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a post-audit section to the persona-template consolidation audit
capturing what came out of the in-prod card-detail test on
rewardflow.lu vs fashionhub.rewardflow.lu:
- Template alignment != data alignment: shared partial guarantees the
markup is the same per persona, NOT that the API response is.
Loyalty's category column rendered empty on merchant + admin
because only the store route enriched category_names. Future
migrations should diff API response shapes per persona, not just
templates. Fixed in d32c1fd5.
- Locale-aware formatters are infrastructure, not per-feature. The
hardcoded 'en-US' bug spanned 27 callsites across 20+ files. Now
swept (dd1f9af8 + 06e59f73 + bb4c4004) and locked down by the
JS-016 architecture rule at error severity (eaf180c6).
- Sweep + rule, not just sweep. Each cleanup should land with a
matching arch rule so the work doesn't decay. Table of the three
rules currently guarding this surface (TPL-016, FE-024, JS-016).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-day update.
- Test 4 (cross-store redemption) verified: card #5's transaction
history now spans store_id=4 (FASHIONHUB, all the earnings) and
store_id=5 (FASHIONOUTLET, today's -100 redemption). Cross-location
flow confirmed.
- Bug found + fixed (478c3a9c) on the storefront auth API. Both
POST /api/v1/storefront/auth/forgot-password and .../reset-password
declared bare `email: str` / `reset_token: str, new_password: str`
params, which FastAPI treats as query strings. The frontend sends
JSON body, so the call 422'd with "missing query parameter email".
Added PasswordResetRequest + PasswordResetConfirm Pydantic body
schemas; switched both endpoints to body: <Schema>. Surfaced
trying to test Test 5's customer login flow.
- /loyalty-wrap skill committed (d03b96da) — mechanises the end-of-day
routine. First invokable as /loyalty-wrap tomorrow (skills load at
session start).
Carries Test 5 into next session (now unblocked by the auth fix), plus
a new TODO from the user: transaction categories should be creatable
by merchants and store owners, not admin-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walks every multi-persona module's templates/{admin,merchant,store}/
and classifies each feature cluster as YES / PARTIAL / NO (legit
exception) / N/A for consolidation. Produces a prioritized 10-item
backlog across 3 waves (~8-9 days of focused work, ~3,100-3,500 LOC
removable).
Headline findings:
- 141 persona templates across 9 modules; loyalty already migrated
with 8 shared partials.
- Wave 1 (low risk, ~1,190 LOC): messaging.messages,
messaging.notifications, billing.billing-history.
- Wave 2 (3-persona my-account is the marquee item, ~1,430 LOC):
tenancy.my-account, tenancy.profile, messaging.email-templates.
- Wave 3 (higher complexity, ~1,820 LOC): tenancy.team,
catalog.store-products lists, customers.customers, tenancy.login
(security-gated).
- Anti-candidates documented inline so contributors don't try to
force-fit them (catalog product forms, marketplace admin vs store,
cms content-page-edit, etc.).
Backend services are uniformly scope-agnostic for every top-10
candidate -- no service/route work required.
Added to mkdocs nav under Proposals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Frontend Error Handling (apiClient)" section to the error
handling guide. Callers can branch on `error.errorCode` and read
`error.details` to localise toasts instead of rendering the raw
English `message`, as already done in the loyalty terminal cooldown
handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-day update. Three things in this session:
- Test 3 (staff stamps/points at terminal) — all 6 sub-steps verified
on prod, including the new cooldown rejection and its localised
toast. Tests 1-3 now done; Tests 4-8 ahead.
- Cooldown bug (93ab072f) + localised toast (aa8ca594) — points-based
programs were silently bypassing program.cooldown_minutes because
points_service.earn_points wrote last_points_at but never read it.
Mirror the stamp check + raise new PointsCooldownException with
error_code POINTS_COOLDOWN. Then localise the terminal toast in
en/fr/de/lb (new cooldown_wait_minutes key) and propagate
error.details through the apiClient so the catch site can render
{minutes}.
- Routing investigation (no fix yet, queued for post-walkthrough) —
user hit a 404 on .../platforms/loyalty/store/fashionhub/dashboard
on the subdomain. Diagnosed 4 distinct bugs from path-based→
subdomain/custom-domain drift (Mount 1 broken, server redirect
store.py:86, JS login.js:155, sidebar URL builder). Ran the full
middleware suite (185 tests pass) — depth on inbound resolution,
zero coverage on outbound URL construction; that's why the bugs
slip through. Scoped a Redirect Trace tool on /admin/platform-debug
+ matching integration tests as the regression net.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document how admin/merchant/store templates share a single shared/ body
partial while keeping their three separate base templates. Covers:
- The wrapper/partial split and why the three base templates must stay
separate (nav + permissions isolation).
- The scope contract: pass strings + booleans only, no macro objects,
no `persona` enum.
- The backend mirror: services scope-agnostic, routes inject scope via
auth deps, same Pydantic shape across personas.
- Legit exceptions and the heuristic for when to keep a template
standalone (multi-tenant aggregators, persona-unique features).
- Forward reference to the TPL-016 architecture rule.
Adds both docs to mkdocs nav under Architecture and Proposals
sections.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-day update. Test 2 (cross-store re-enrollment at FASHIONOUTLET
with the email from Test 1) walked cleanly with all behavioral checks
green:
- exactly 1 loyalty_cards row, no duplicate
- zero new email_logs rows (no duplicate welcome email)
- cross-location locations block lists both stores
- title already-enrolled branch renders correctly
One copy bug surfaced and was fixed in dee2eab2: the title at
enroll-success.html:21 was already x-text-conditional on
already_enrolled, but the subtitle below was a static
{{ _('success.message') }} so two contradicting messages stacked
("You're already a member!" + "You're now a member..."). Made the
subtitle conditional the same way and added a new
already_enrolled_message i18n key in en/fr/de/lb.
Test 2 marked done. Carries forward Test 3 plus the existing follow-ups
(Hetzner doc check, B1-F unit tests, prospecting tasks/__init__ fix,
other-module email audit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-day update. Adds a new section to the go-live readiness doc
covering today's three shipped commits:
- 236fee01 — enrollment-success CTA rename (Continuer mes achats
-> Retour à l'accueil)
- ab3e133a — flatpickr birthday picker so Firefox honors dd/mm/yyyy on
FR. Firefox-specific limitation (Mozilla bug #1344625) — Chrome /
Safari / Edge already respected <html lang="fr"> from earlier fix.
- 5f288502 — admin program form now warns when terms fields are both
empty and disables save until at least one is filled, so a
merchant can't accidentally ship a program with a non-clickable
Conditions Générales link on the storefront.
Marks Test 1 fully done (all 6 originally-reported bugs B1-A..B1-F
plus today's 2 follow-up nits resolved end-to-end on prod). Carries
forward the remaining items from yesterday's queue: Test 2, Hetzner
doc check, unit tests for the B1-F chain, prospecting tasks/__init__
fix, and the other-module email-path audit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `static_v(request, name, path=...)` Jinja helper that appends
?v=<commit-sha> from app.core.build_info, plus a CachedStaticFiles
subclass that serves Cache-Control: public, max-age=31536000, immutable
in production and no-cache in development. Browsers refetch JS/CSS
automatically on every deploy without the user having to hard-reload.
- New: app/core/static_files.py (CachedStaticFiles)
- Updated: app/templates_config.py (static_v helper)
- Updated: main.py (use CachedStaticFiles for *_static mounts)
- Codemod: 143 url_for('*_static', path='*.js'|'*.css') → static_v(...)
across 123 templates. Images/fonts/JSON locales intentionally
unchanged (out of scope).
- Arch rule: FE-024 (warning) flags raw url_for on JS/CSS to prevent
drift. Note: FE-008 was already taken by the number_stepper rule.
- docs/proposals/static-asset-cache-busting.md marked Done.
Closes plan from docs/proposals/static-asset-cache-busting.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-day 2026-05-17 update to the go-live readiness doc.
Welcome email B1-F is now fully resolved end-to-end (enrollment ->
celery dispatch -> email_logs status=sent -> emails landing). The
issue was a chain of four nested bugs each masking the next — the
doc lists all four with commit hashes (44c42909, 3e650ff8, 2a216101,
5b21908b) plus the three earlier same-session fixes for the SMTP
password eye toggle, the JS error on /admin/loyalty/programs, and
the 422 on ProgramCreate.
Also captured:
- Audit finding: prospecting/tasks/__init__.py has the same bug as
bug #3 (scan_tasks.py exists but not imported).
- Six queued follow-ups for next session: two Test 1 storefront nits
(date format on FR, "Continuer mes achats" CTA), Test 2 cross-
store re-enrollment, Hetzner doc check, a concrete unit-test list
that would have caught each of the four B1-F bugs, the prospecting
__init__.py fix, and a wider audit of every module's email path
to find any other silently-broken @shared_task registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proposes a one-liner Jinja helper that appends ?v=<commit-sha> to
static JS/CSS URLs, leveraging the existing build_info pipeline so
users no longer need to hard-reload after every deploy. Documents the
codemod scope (143 callsites), open decisions, and the server-side
Cache-Control: immutable follow-up that makes the version flip pay off.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a short "Next session" subsection to the 2026-05-16 update
spelling out the three steps to pick up Test 1 round 2 (SMTP
re-apply post-reset, program sanity check, live log tail + fresh
enrollment).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/proposals/loyalty-go-live-readiness.md — record the
2026-05-16 session: 7 bugs found during Test 1 round 1
(TimestampMixin, CardDetailResponse, storefront i18n triple,
Makefile, meta_keywords), 6 fixed and deployed with commit
hashes, B1-F still pending repro on the clean DB.
- docs/deployment/hetzner-server-setup.md — fix section 12
step 8 to call seed_email_templates_core.py +
seed_email_templates_loyalty.py instead of the non-existent
seed_email_templates.py, and add seed_demo.py at the end.
Append a note about post-reset state (.build-info + admin
settings).
- mkdocs.yml — sweep 14 previously-unlinked proposals and 2
module docs (loyalty/production-readiness.md,
prospecting/batch-scanning.md) into the nav so the strict
build no longer warns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Practical field-by-field intake doc for the first merchants — maps
each question to its DB column so the call → admin UI is 1:1.
Includes a section on marketing-material reuse aimed at franchisees,
with written-authorization clauses for the future marketing module.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The web user-journey checklist (Tests 1–8) only covers human-using-loyalty
flows from a browser. The cashier-facing Android tablet built in Phases
A–F goes through a different surface and has its own failure modes that
won't surface in any web test. Adding 6 dedicated Android tests so a
tablet-in-hand verification has the same level of structure as the web
side.
- Test 9: Tablet pairing — QR scan + manual entry fallback, with the
audit (paired-device row appears, last_seen_at populated)
- Test 10: PIN screen — wrong/right PIN, offline-capable bcrypt verify,
locked-PIN rejection
- Test 11: Daily flows — search, scan, enroll, stamp, earn, redeem,
with the acting_terminal_device_id audit column check at the end
- Test 12: Offline queue + sync — airplane mode → queued → re-online →
drain; redeem is hard-disabled offline per spec
- Test 13: Auto-lock + manual lock — 2 min idle, immediate lock button,
the known caveat that AlertDialog pointer events don't bubble
- Test 14: Device revocation — revoke on web → 401 on tablet next call
Updated the go-live readiness snapshot to reference these as Step 6b
(gated on user obtaining a tablet, not on schedule).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures where the loyalty pre-launch checklist actually stands after
tonight's prod readiness pass:
- Step 1 (email templates seeded) ✅
- Step 2 (Google Wallet config) ✅ validated via wallet-debug
- Step 3 (migrations) ✅ all module heads incl. loyalty_011 on prod
- Step 7 (Wallet real-device test) ✅
- Steps 4, 5 (FR/DE/LB analytics keys, store-owner template
permission) deferred — cosmetic / non-blocking
- Step 6 (8 user-journey E2E tests) is the remaining human gate
- Step 9 (Google Wallet production access) post-launch
Also records the SMTP path-change diagnosis (own mail server on port
465 blocked outbound from Hetzner; switched to 587 STARTTLS via
/admin/settings DB overrides) and the cosmetic fix shipped in
f2d1bdcd so the test email reports the *effective* config.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full implementation plan for the RewardFlow Terminal Android app:
4 screens (Setup, PIN, Terminal, Enrollment), 6 phases (~5 days),
ASCII wireframes, data layer design, offline queue strategy,
multi-language support, and API model changes needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client requirement: sellers must select a product category (e.g.,
Men, Women, Accessories, Kids) when entering loyalty transactions.
Categories are per-store, configured via admin/merchant CRUD.
Proposal covers: data model (StoreTransactionCategory + FK on
transactions), CRUD API for admin + store, web terminal UI, Android
terminal integration, and analytics extension path.
Priority: urgent for production launch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename apps/ → clients/ for clarity:
- app/ (singular) = Python backend (FastAPI, server-rendered web UI)
- clients/ (plural) = standalone client applications (API consumers)
The web storefront/store/admin stays in app/ because it's server-
rendered Jinja2, not a standalone frontend. clients/ is for native
apps that connect to the API externally.
Updated:
- docs/architecture/overview.md — added clients/ to project structure
- clients/terminal-android/SETUP.md — updated path references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move store theme admin pages, templates, and JS from tenancy module
to CMS module where the data layer (model, service, API, schemas)
already lives. Eliminates split ownership.
Moved:
- Route handlers: GET /store-themes, GET /stores/{code}/theme
- Templates: store-theme.html, store-themes.html
- JS: store-theme.js, store-themes.js
- Updated static references: tenancy_static → cms_static
Deleted old tenancy files (no remaining references).
Menu item in CMS definition already pointed to correct route.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Content page editor improvements:
- Page type selector: Content Page / Landing Page dropdown (sets template)
- Title language tabs: translate page titles per language (same pattern as sections)
- Content language tabs: translate page content per language
- Meta description language tabs: translatable SEO descriptions
- Template-driven section palette: template defines which sections are available
(store landing pages hide Pricing, platform homepages show all)
- Hide content editor when Landing Page selected, hide sections when Content Page
Schema changes (migration cms_003):
- Add meta_description_translations column (JSON) to content_pages
- Drop meta_keywords column (obsolete, ignored by all search engines since 2009)
- Remove meta keywords tag from storefront and platform base templates
API + service updates:
- title_translations, content_translations, meta_description_translations
added to create/update schemas, route handlers, and service methods
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
E-commerce nav (Products, Cart, Account) shows on hosting POC sites.
Preview mode should render only CMS pages (Services, Projects, Contact)
in the nav, not module-defined e-commerce items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Details the gap between scraped content (21 paragraphs, 30 headings,
13 images) and what appears on POC pages (only placeholder fields).
Phase 1 plan: programmatic mapping of scraped headings/paragraphs/
images into template sections (hero subtitle, gallery, about text).
Phase 2: AI-powered content enhancement (deferred, provider TBD).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deleting a HostedSite leaves the Store orphaned, blocking subdomain
reuse. Proposal: cascade delete the Store when deleting the site.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master plan covering 4 workstreams:
1. Fix hosting foundation (merchant/prospect required)
2. Security audit pipeline + report + live demo
3. POC builder with industry templates (restaurant, construction,
auto-parts, professional-services, generic)
4. AI content enhancement (deferred, provider TBD)
Target: 10-step journey from prospect discovery to live website.
Steps 1-3 work today, steps 4-10 need the work described.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hosted sites leverage existing CMS module (ContentPage, StoreTheme,
MediaFile) instead of building a separate site rendering system. Industry
templates (restaurant, construction, auto-parts, professional-services,
generic) are JSON presets that populate CMS entities for a new Store.
POC phase uses subdomain routing (acme.hostwizard.lu), go-live adds
custom domain via StoreDomain (acme.lu). All routing handled by existing
StoreContextMiddleware + Caddy wildcards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4-phase plan for integrating scripts/security-audit/ into the
prospecting module: security audit pipeline, report generation,
live demo server, and POC site builder architecture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Email clients need absolute URLs to make links clickable. The
acceptance_link was a relative path (/store/invitation/accept?token=...)
which rendered as plain text. Now prepends the platform domain with
the correct protocol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- hetzner-server-setup: runner timeout 3h, shutdown_timeout 300s,
deploy.sh now writes .build-info and uses explicit -f flag
- gitea: document unit-only CI tests and xdist incompatibility
- docker: add build info section, document volume mount approach
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add docker-compose.override.yml exposing db/redis ports for local dev
- Remove override from .gitignore so all devs get port mappings
- Use explicit -f in deploy.sh to skip override in production
- Document production safety rule: always use -f on the server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- deploy.sh writes .build-info with commit SHA and timestamp after git pull
- /health endpoint now returns version, commit, and deployed_at fields
- Admin sidebar footer shows version and commit SHA
- Hetzner docs updated: runner --config flag, swap, and runner timeout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security:
- Fix TOCTOU race conditions: move balance/limit checks after row lock in redeem_points, add_stamp, redeem_stamps
- Add PIN ownership verification to update/delete/unlock store routes
- Gate adjust_points endpoint to merchant_owner role only
Data integrity:
- Track total_points_voided in void_points
- Add order_reference idempotency guard in earn_points
Correctness:
- Fix LoyaltyProgramAlreadyExistsException to use merchant_id parameter
- Add StorefrontProgramResponse excluding wallet IDs from public API
- Add bounds (±100000) to PointsAdjustRequest.points_delta
Audit & config:
- Add CARD_REACTIVATED transaction type with audit record
- Improve admin audit logging with actor identity and old values
- Use merchant-specific PIN lockout settings with global fallback
- Guard MerchantLoyaltySettings creation with get_or_create pattern
Tests: 27 new tests (265 total) covering all 12 items — unit and integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix IPv6 host parsing with _strip_port() utility
- Remove dangerous StorePlatform→Store.subdomain silent fallback
- Close storefront gate bypass when frontend_type is None
- Add custom subdomain management UI and API for stores
- Add domain health diagnostic tool
- Convert db.add() in loops to db.add_all() (24 PERF-006 fixes)
- Add tests for all new functionality (18 subdomain service tests)
- Add .github templates for validator compliance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update origin cert config: wildcards for omsflow.lu, rewardflow.lu, hostwizard.lu
- Add wildcard Caddy blocks to production Caddyfile example
- Replace "Future" section with actionable runbooks:
- Add a Store Subdomain (self-service, no infra changes)
- Add a Custom Store Domain (Cloudflare + Caddy + DB)
- Add a New Platform Domain (full setup)
- Document wizard.lu exception (no wildcard due to git.wizard.lu DNS-only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The setting `settings.platform_domain` (the global/main domain like "wizard.lu")
was easily confused with `platform.domain` (per-platform domain like "rewardflow.lu").
Renamed to `settings.main_domain` / `MAIN_DOMAIN` env var across the entire codebase.
Also updated docs to reflect the refactored store detection logic with
`is_platform_domain` / `is_subdomain_of_platform` guards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>