The live record carries placeholder junk in Linkedin/upwork/Image ('1',
'ieivmermi', 'gineoirn'). With type=url the whole form went invalid and the owner
could not save anything. type=text inputmode=url keeps the URL intent and the
mobile keyboard without hard-blocking on data the owner is trying to correct.
- hide the inert client request form; announce it as coming soon with an email
fallback (no reachable error state, no submit that refuses a completed brief)
- soften the contact claim to 'where a freelancer published their details' and
make a card with no contact say so instead of showing an empty space
- move the admin add/edit panel off the public page to its own draft page
(/odoo-community-developers/admin/), unlinked, reachable only by URL
- admin edit: load-and-confirm the record before writing, and read-modify-write
the booleans so an untouched OCA_member/is_individual keeps its current value
- application form: add Country + Odoo versions (the filters' inputs), GitHub and
OCA profile URLs, and an explicit email-publication consent line
- tighten 'How this works' from ~120 words to two short options + one caveat
- build-directory.py: skip records with no Name; render GitHub/OCA links and the
no-contact line; read Country via alias
Odoo freelancer directory for our clients, built as a draft (noindex, out of
sitemap.xml and llms.txt, no inbound links, no announcement surface, no tracking).
- The list is rendered at BUILD TIME from the single freelancer webhook
(GET/POST .../odoo_experts) so it works with JavaScript disabled; the page
script only filters that HTML.
- Filters (country, odoo_versions, languages, specialities) are printed only when
the live data actually supports them, so a filter that returns nothing never
ships. No record has them yet, so none render today.
- Three forms: freelancer application (POST, no id -> create), admin add/edit
(POST with id -> update), and a client request form whose endpoint is a single
inert constant until the workflow exists.
- No reviews, no ratings, no claims beyond what a freelancer wrote and the count
from our own records. No competitor named.
New post blog/posts/odoo-20-is-live-on-our-servers-here-is-what-changed.html
(chrome copied from blog/posts/track-the-oca-modules-you-actually-use.html,
body verbatim from the inbox source), plus the blog/index.json entry (first),
one sitemap.xml <url> and one llms.txt blog line.
Restore /odooityourself.html from ada11e4^ — the exact content that was
live when commit ada11e4 deleted it on 2026-11-01. That revision already
includes the three later edits that were in the live page:
4da2f1c remove the hero CTA
3e913d6 remove the trial MCP endpoint line
0cd0fa1 Plausible snippet on every page
Byte-identical to ada11e4^:odooityourself.html
(sha256 20561121ba14ff18e482eb2e40b49085cf9a5586885483bda427d647ed19bc4d,
27475 bytes / 704 lines).
Deliberately stays OUT of search: it is an affiliate's page (Andrew), not a
search target (decision 2026-10-30). No sitemap.xml and no llms.txt entry
added — both still contain 0 references. No inbound links added.
The two SVGs it uses (images/andrew/ai-agent-on-server.svg and
ai-agent-on-server-mobile.svg) were kept at deletion and are still present.
No other change: no copy edits, no redesign, no tracking, no consent gate.
verify-html.py PROBLEMS: 0; competitor QC exit 0.
Same defect the bootcamp carried (2aa9b19), fixed site-wide in one mechanical pass.
P0 (17 pages): /style.css was deferred via media="print" onload=... -> unstyled
first paint -> a BODY layout shift. Now a normal render-blocking
<link rel="stylesheet">; the now-redundant no-JS <noscript> for style.css is
dropped. Web fonts + Font Awesome keep their legitimate deferral.
Pages: index.html, odoo-black-friday/, odoo-hosting-offers/, de/index.html,
es/index.html, odoo-hosting-2027.html (+de/+es), odoo-community-upgrade.html
(+de/+es), odoo-staging-environment/, odoo-community-ai/,
odoo-n8n-masterclass/, australia.html, workshops/, what-can-i-use-odoo-for/.
P1 (1 page): /odoo-black-friday/ footer form action mailto: -> CRM webhook
(Lighthouse is-on-https). Changed only because the page already loads a handler
for #footerContactForm (static/black-friday.js). The other 13 mailto-form pages
load no handler and are left alone on purpose (no drive-by handler copy-in).
Added the no-JS <noscript> fallback so a JS-off visitor keeps a path.
P2 (35 pages): add a single <main> landmark wrapping the primary content; top
bar and footer stay outside. Additive only, no redesign.
Guards held: bootcamp EN/DE/ES and every JS/static file untouched; Buy forms
product_id 4/17 intact; one [data-bc-seats]; ads-consent gate intact.
verify-html.py PROBLEMS 0; competitor QC 74 pre-existing, 0 added.
PageSpeed pass on /odoo-erp-automation-bootcamp/ (EN + DE + ES):
- CLS: /style.css was deferred via media="print" onload=... → unstyled first paint
shifted BODY 0.31 (live CLS 0.326). Now a normal <link rel="stylesheet">; the two
hero webp files are preloaded (fetchpriority=high) by viewport.
- a11y: footer <h4> → <h3> (heading-order), a lighter brass + underline for links
inside on-dark notes (color-contrast, link-in-text-block), .fcopy 3.2:1 → 5.17:1.
- best-practices: the footer contact forms and the waiting-list form now post to the
CRM webhook instead of action="mailto:", which Lighthouse flags as an insecure
request (is-on-https 0 → 1). No Google Ads tag on these pages (no consent gate).
- regression fix: these pages do NOT load /script.js, so after the action change the
footer form had no JS handler and a submit navigated the visitor to the webhook's raw
JSON reply. static/bootcamp-waitlist.js now carries a footer-form handler mirroring
/script.js one-for-one (same webhook, payload { email, requirements, area:'message' }
in UTM.inject, success/failure behaviour); its two strings are read from the form's
own data-ok / data-err attributes so each language keeps its own text. ENDPOINT
hoisted to module scope so both handlers share it.
- no-JS path: a <noscript> footer line with support@ODOO4projects.com as a plain
mailto link. Verified it does not re-trigger is-on-https (unlike a mailto FORM action).
Guards held: Buy forms still post product_id 17; exactly one [data-bc-seats];
waitlist still posts area:'message'; plausible snippet present; competitor QC exit 0.
The [data-bc-seats] line lived in the Price-and-booking section; the user wants it
just under the hero. Moved (not copied) on EN/DE/ES — one element per page, the same
/static/bootcamp-availability.js, every string unchanged ("x of 6 seats available" /
"x von 6 Plätzen frei" / "x de 6 plazas disponibles").
Also adds a page-scoped :disabled style for the Buy buttons so a sold-out cohort's
disabled buttons actually read as disabled (rule: zero is honest — the buy must not
look open). Buy forms untouched: still post product_id 17.
Three bootcamp pages (EN/DE/ES) get a small waiting-list block below the Buy
button, in the Price and booking section next to the live seat count.
- two required fields: email + comment; framed as "another timezone or
language?" and worded around the January cohort's own schedule
- posts to the same n8n CRM webhook as the site's other forms, with the
footer-message payload shape copied exactly: area:'message',
requirements carries the comment, no name field, wrapped in UTM.inject()
- success hides the form and shows a confirmation; a failure restores the
button and shows a retryable error (never stuck on "sending")
- Plausible event 'Bootcamp Waitlist' on success only (cookieless); no
Google Ads tag and no conversion, as on the rest of these pages
- new page-local /static/bootcamp-waitlist.js carries the verbatim UTM
helpers from script.js (this page does not load script.js)
- buying is untouched: no markup change to the three product-17 forms
Verified: exact posted body per language captured on the wire, success and
error states in a real browser, 375 px no overflow, verify-html 0, JS syntax
0, competitor QC 0 new, impeccable detect byte-identical (zero new findings).
Measured live from the browser: the webhook answers in ~2.2-2.6s warm but a
cold first connection can exceed the old 3s abort, so the count would fall
back to the static text on a fresh visit. 6s is bounded and nothing blocks
the buy either way.
Show the honest remaining seats for product 17 (Bootcamp Q1-2027) on all
three bootcamp pages, in the page's own language, from the availability
webhook. Progressive enhancement (static/bootcamp-availability.js): empty
and hidden by default, filled on a successful fetch; on qty 0 the cohort is
reported full, the next cohort is pointed at, and the Buy buttons are
disabled; on any timeout/error/unexpected payload the page is left exactly
as-is and the Buy buttons stay enabled. The count is never written into the
HTML as a fixed string.
All nine Buy forms on the bootcamp pages (EN/DE/ES, three each) posted
product_id 12, which the shop resolves to 'Bootcamp - Q1-2026-eng'. The
advertised cohort is Q1 2027 (starts Thu 21 Jan 2027), so a customer
buying the 2027 bootcamp received the 2026 cohort. Both are 595.00 and
neither cart was empty, so it went unnoticed; the form's info field reads
'Q1 2027, English' while the product was 2026.
product_id 12 -> 17 in every bootcamp Buy form, all three languages.
The info field is kept (it renders as the cart line description via
sale.order.line.name, so it is read downstream); it is truthful.
static/buy.js doc comment updated to match. Nothing else changed:
plausible-event-name=Buy class, target=_blank form post, prices and
layout untouched.
New post blog/posts/from-a-spreadsheet-to-a-running-erp.html (area news, 2026-11-02),
body pasted verbatim from the inbox source (sha256 59568fc8…). Chrome copied from the
newest post (oca-paraguay-internationalization-hackathon.html), inline style trimmed to
the rules the page uses. Card image screencast.png wired in three additive places
(post-image after the meta line, og:image, blog/index.json image key) plus BlogPosting
JSON-LD. blog/index.json 17 -> 18, sitemap.xml 42 -> 43 <loc>, llms.txt 17 -> 18 lines,
all text splices (16 insertions, 0 deletions).
The coupon applies today (signed-out test: post_basket -> /coupon/HOST27 ->
cart 295.00 from a 395.00 list), so the offer starts today, not on 16 November.
- index.html: one seasonal banner above the hero, a single <a> to
/odoo-black-friday/. Homepage components and CI only (midnight/brass/cream,
Marcellus/Inter). No script, no tracking tag, no event: the homepage has no
consent gate, so nothing measurable may be added there. Hero, server
selector, section order and JSON-LD untouched. Dated for removal after
30 November 2026.
- odoo-black-friday/: every start claim 16 -> 2 (meta, og:image:alt, JSON-LD
WebPage description + two FAQ answers, hero-sub, two body FAQ answers).
End date, countdown instant, forms, consent gate and the three conversions
unchanged.
- odoo-hosting-offers/: same start claim on the second ad lander, so the AG1
visitor is not told the deal begins in two weeks.
Every price claim keeps the "first year" qualifier.
Move the homepage's Server Location lockup (label + select) out of section#pricing
and into the HOST27 deal card's CTA row, next to the Buy button. There is still
exactly ONE element with id pricingServerLocation on the page, so buyNow() — which
resolves the selector by id — and every pricing button keep working untouched.
The pricing block keeps only the Odoo Version selector and reads the same global
location selection.
- markup: new .bf-cta-row in the deal form (selector left, brass Buy right);
location lockup removed from .pricing-server-row
- CSS (page-scoped): .bf-cta-row / .bf-cta-location, and the light-on-ivory twin
of .pricing-server-select (style.css carries neither; the homepage styles it
inline for its dark section). Wraps at 375px with no horizontal overflow; the
Buy stays the one dominant action.
- static/black-friday.js: on submit of the native post_basket forms, write the
chosen city + version into the basket line's `info`, exactly as buyNow() does.
Additive only — with JavaScript off the POST is unchanged (no info) and the
selector is empty because its options are built here (pre-existing behaviour).
Untouched: the hidden redirect=/coupon/HOST27, the single plausible-event-name=Buy
mark, the price, the countdown, #trialForm, the booking link and the consent gate.
Verified: verify-html.py PROBLEMS 0; competitor QC exit 0 on the page (74
pre-existing site-wide, 0 added); node --check OK; impeccable detect 35 findings,
identical set to HEAD (0 introduced); live browser: fastest server auto-selected,
wire body basket=[{"product_id":4,"qty":1,"info":"boston|20.0"}] and it tracks the
selectors on repeat submits; 375px no overflow.
The offer row was a centred pair (620 + 300), so the trial/booking column ended at
1104 px while the server grid's last cell (Sydney) ended at 1156 px — a 52 px gap
against the only edge on the page it should match.
The row is now two columns: the aside takes a fixed 300 px and sits on the
container's right content edge; the left column takes the rest, so the deal box is
centred in the space left of it rather than parked in its own 620 px lane. The row's
column gap is folded into that centring, and the deal is capped at the column width
minus one gap so the two boxes can never touch just above the 900 px stack.
CSS only: no markup, no ids, no forms, no JS. Buy form, hidden redirect, countdown,
Buy mark, #trialCard/#trialForm, the booking <a> and the consent gate are untouched.
Measured 1280 px: aside right 1156.4 == Sydney right 1156.4; deal gaps 63.9 / 64.0.
On /odoo-black-friday/ the offer row is now two columns at >=901px: the deal (left,
unchanged and still the single dominant primary) with the free trial and a booking
link beside it (right, deliberately smaller and quieter).
- Trial: the existing #trialCard/#trialForm moves verbatim from the features grid into
the row's aside. Same ids, same static/black-friday.js handler, same consent gate and
label (AW-18065522009/AtAPCIjjvrAdENn6p6ZD) — no second trial mechanism.
- Booking: a plain <a target=_blank rel=noopener> to Oliver's verified Google
appointment page (no iframe). Its click fires AW-18065522009/P0j5CPiCv7AdENn6p6ZD
through the same gate via window.o4pAds.bookConversion; the click is never
preventDefault-ed, so a blocked tag can never stop the link opening.
- ads-consent.js gains the booking label (from the page, not hard-coded) and the
bookConversion hook; the buy path is untouched.
Deal buy stays the dominant action; ONE plausible Buy mark; one price; countdown pair
unchanged.
Adds the second Google Ads label to /odoo-black-friday/: the free-trial
form (#trialForm) now fires AW-18065522009/AtAPCIjjvrAdENn6p6ZD (action
7886320008 "Registrierung") when it is submitted.
Reuses the existing gate in static/ads-consent.js rather than a second
mechanism: the label is read from the page (data-ads-trial-send-to on
#adsConsent), so the script stays generic. The page hands the gate its
real submit function via window.o4pAds.trialConversion; the gate fires
the conversion and runs the submit from the event callback, with the same
700 ms hard timeout that runs it anyway. Nothing fires and no cookie is
set before consent (identical default-deny gate).
The buy conversion is untouched (comments only).
Tag id + conversion label now live as data-ads-id / data-ads-send-to on the banner
element, so the served HTML carries the tag config and the script stays reusable
(missing config -> inert). Re-verified: no cookie/request before consent, cookies +
tag after Accept, conversion + form submit on click, 700 ms fallback submit with the
tag blocked.
/odoo-black-friday/ only, page-side. No Odoo change, no ad-account change.
- static/ads-consent.js (new): default-DENY consent; nothing loads and no request
fires until Accept. On Decline (or switching to Decline later) the _gcl_au/_gcl_aw
cookies are cleared. A footer "Cookie settings" link reopens the banner.
- conversion: on submit of either /shop/post_basket buy form (offer block + On the
Rise card), gtag('event','conversion',{send_to: AW-18065522009/vnDUCPDxt7AdENn6p6ZD})
then form.submit() from event_callback, with a 700 ms hard-timeout fallback so a
blocked tag can never swallow the buy. No consent / no tag -> native POST untouched.
- privacy.html: new "Advertising cookies" disclosure (what, why, how to decline).
- Plausible Buy marks and the checkout-side Purchase event untouched.
The DE/ES hero-sub read '... Zertifikat und Upgrades.' / '... el certificado y
las actualizaciones.' — the last unqualified upgrade instances, mirroring the EN
hero fix (8fc6478). Both pages already qualify the distinction in the body and
the comparison table; the hero now matches, naming the in-version scope with the
pages' own phrase (DE 'die Updates innerhalb Ihrer Odoo-Version' / ES 'las
actualizaciones dentro de su versión de Odoo').
In-version updates/upgrades stay included; a major-version migration (19 -> 20)
remains a separate quote. Copy-only: tag/attr/JSON-LD structure proven identical.
The hero-sub read 'the certificate and the upgrades' unqualified while the
same page qualifies the distinction twice (body + comparison table). Replaced
with the page's own qualified phrase, 'the updates within your Odoo version'
(no new vocabulary, text-only). In-version updates/upgrades stay included; a
major-version migration (19 -> 20) remains a separate quote.
Copy-only: 2 lines, tag/attr/JSON-LD structure proven identical.
Qualify the version-upgrade claim in the three spots the EN page (46b943b) already
qualifies, in the DE and ES twins at exactly the same three places:
- JSON-LD Product.description
- "Why we exist" body paragraph
- comparison-table row (Version upgrades / Versions-Upgrades / Actualizaciones de versión)
In-version / server-side updates and upgrades are included; a major-version
migration (Odoo 19 -> 20) is a separate service and is quoted separately
(user rule 2026-11-02, wiki concepts/odo4-commercial-promises). Copy-only: no
price, plan, structure, tag, attribute, class or link changed.
Fix 1: the On the Rise pricing card quoted $395 while the deal block said $295 first
year — a price contradiction within one scroll. On this page only the card now reads
$295.00 / 'for your first year, then $395 a year'. Other pages keep $395.
JSON-LD Offer price was already 295.00 and still matches the visible price.
Fix 2: the three list-price Buy Now buttons (data-id 6/4/5) competed with the deal.
Side Hustle and Powerhouse buy buttons removed on this page. The On the Rise card's
buy is now the deal purchase — the same one-click checkout as the offer block
(product 4, /coupon/HOST27?r=/shop/checkout), native POST so it works with JS off.
Tracking: the offer-block form is the single element carrying the Plausible Buy class;
the Bootcamp CTA and the two list-price buttons no longer carry it.
Fix 10: a time-remaining element near the offer and near the buy, computed client-side
from one documented constant (2026-11-30T23:59:59-05:00, US/Eastern). The static
sentence 'Ends Monday 30 November 2026.' still reads with JS off; past the instant the
text switches to the past tense. No visibility animation.
The four 'version upgrades are run for you' lines (black-friday, hosting-offers,
hosting-2027 body + table row, llms.txt) and their JSON-LD descriptions now say
updates and in-version upgrades are included, and name a major-version migration
as a separate, quoted service. terms.html: migration = separate service, quoted
separately; release list no longer reads stale (names Odoo 20 as current).
No offer, price, page structure or ad change.
The archived two-offer alt named a Bootcamp line the current card does not carry
(re-rendered hosting-only in e6f0b6a). The meta + og:description do carry the Bootcamp
again (the page shows it); the image alt stays true to the image.
- Hero is the homepage's (same markup/assets): slide-in city image + the server
location selector below it that slides a chosen city in; headline adapted to the offer.
- Order now: hero > location selector > deal (HOST27, $295 first year / $395 after,
one-click product-4 buy with redirect=/coupon/HOST27?r=/shop/checkout) > trust bar >
Black Friday FAQ > features + trial card > FAQ > pricing (buy section) > Bootcamp > footer.
- Bootcamp box restored from webmaster/out/2026-11-02-bootcamp-box-removed-archive.md:
heading, USD 595.00, mechanism line, deadline, CTA, its meta/og contribution, its JSON-LD
Product entry and the FAQ answer are all back and consistent.
- Excluded (no blog, no OCA modules, no Project Plan); no dangling refs.
- New /static/black-friday.js = the homepage behaviours this page needs (root-relative
image paths), with the blog/OCA/project-plan blocks removed.
- Canonical URL unchanged; competitor QC 0 on the page.
Two user-requested changes on the Black Friday landing page.
1. The one-year framing is now unmissable where the price is stated:
"$295 for your first year — then $395 a year after that." (was: "$395.00 a year —
$295.00 for your first year"). The hero, meta description, og:description and the
JSON-LD WebPage description all carry the same first-year-then-$395 wording. The
offer itself is unchanged ($295 first year, $395 after, code HOST27, 16-30 Nov 2026).
2. The Bootcamp box is removed; the page is now a hosting-only offer surface. The
meta/og, the JSON-LD Product entry and WebPage.about Thing were rewritten, the FAQ
answer no longer claims the Bootcamp is part of the offer (HTML + JSON-LD, byte-
identical), and the offer grid re-flows to one centred card (no empty cell).
The removed box is archived, with line numbers, at
webmaster/out/2026-11-02-bootcamp-box-removed-archive.md.
The OG card was re-rendered hosting-only (sub line "your first year"); og:image:alt
updated to match.
Verified: verify-html.py exit 0; competitor QC on the page exit 0; impeccable detector
0 new findings (23 -> 21); JSON-LD valid, 1 Product, FAQ 6/6 byte-identical.
The Hosting form on /odoo-black-friday/ posts product 4 ("basket") + clear=1
to the shop's custom /shop/post_basket route. Adding a third hidden field,
redirect=/coupon/HOST27?r=/shop/checkout, chains that route's redirect into
the core website_sale_loyalty GET /coupon/<code> handler, so a single click
lands the visitor on /shop/address with the -$100 first-year discount already
itemised (Total $295) - no typing, works with JavaScript off, and it survives
the cross-host POST because the target is baked into the form.
Mechanism designed, probed and proven signed-out end to end by odoo-dev
(wiki/concepts/odoo-shop-promo-autoapply.md). No module, no price change.
Rollback: delete the one <input type="hidden" name="redirect"> line.
The code was behind a reveal (click 'Open the Black Friday price'). Both offer
cards are now always open: the price and HOST27 are readable without any
interaction. The scroll animation (opacity/transform only, base state fully
opaque, behind @supports) is presentation, never a gate.
The hosting CTA is now a native form that posts the On the Rise product straight
to the shop basket, so one click from the page lands in checkout with the plan in
the cart — replacing a three-navigation detour (offers hub -> homepage trial ->
pricing).
Bootcamp box content is untouched; only its reveal gate was removed.
The two Black Friday landing pages carried 0 Buy marks, so the page we are
about to advertise reported nothing. Buy is a CLICK PROXY by contract (it marks
a click on the offer CTA, not a completed purchase); the real purchase is fired
on the checkout host (20.odoo4projects.com) by odoo-dev. Both CTAs on
/odoo-black-friday/ and the BF-block link on /odoo-hosting-offers/ now carry
plausible-event-name=Buy, each labelled in-page as the proxy it is.
No Google Ads tag added: see handoff 2026-11-01-webmaster-to-olchi-ads-tag-and-buy-proxy.md
(privacy.html discloses no advertising cookies and the site has no consent gate).
- de|es/index.html: order widget #orderPlanTax '+ MwSt.'/'+ impuestos'
-> 'inkl. MwSt.'/'impuestos incluidos' (static span, no JS writes it)
- de|es/support.html: add the AI-agent urgent + 24h email support sentence
- de|es/odoo-erp-automation-bootcamp/index.html: add the seat terms
(transferable any time, cancellable up to two weeks before 21 Jan 2027)
- terms.html has no DE/ES page (legal pages held back) -> nothing to mirror
- terms.html: cancel any time + refund unused time (pro rata); delete the
no-refund sentence and the 'no refund promise beyond' clause
- terms.html: prices are tax included (was 'plus tax where it applies')
- index.html: order widget #orderPlanTax text '+ tax' -> 'tax included'
(id/class kept; static text, no JS reads or writes it)
- support.html: add the AI-agent-urgent + 24h email support promise,
bot still scoped to quick fixes
- odoo-erp-automation-bootcamp/index.html: seat transferable any time,
cancellable up to two weeks before the 21 Jan 2027 start
Mirror of 9715a3b into the two translated bootcamp pages. Each page's three
surfaces now read 595.00: the three buy buttons, the JSON-LD Offer `price`
field, and the `.bc-price` sentence. Matches the shop product, so the DE/ES
page can no longer lose the sale at the cart.
Price-only: the term (Q1 2027), the dates, the six-seat cap and the "one year
of hosted Odoo Community Edition" wording are untouched. Currency stays USD,
numeral format unchanged per page.
Verified (see translator/work/verify-price-change.py):
- HEAD vs worktree per page identical on tag sequence, attribute list,
<style> bytes, non-JSON-LD <script> bytes and JSON-LD shape -> text-only.
- 595.00 x5, 465.00 x0, no stray `465` token on either page.
- the three buy forms label-normalised byte-identical to EN.
No DE/ES workshops, black-friday or what-can-i-use-odoo-for translations
exist; llms.txt already carries 595.00.
The ODOO ERP Automation Bootcamp price is now USD 595.00 (shop product
already set). Every printed EN bootcamp price now reads 595.00 so the page
no longer loses the sale at the cart (page said 465.00, cart charged 595.00).
EN surfaces changed (price strings only — term, dates, six-seat cap and the
one-year hosted Odoo Community wording untouched):
- odoo-erp-automation-bootcamp/index.html: 3 buy buttons, JSON-LD Offer
price, .bc-price sentence
- odoo-black-friday/index.html: Bootcamp box JSON-LD Offer price, bf-teaser,
bf-price
- workshops/index.html: both meta descriptions, JSON-LD Offer price,
.bc-card-price, comparison-table cell
- what-can-i-use-odoo-for/index.html: the line naming the bootcamp
- llms.txt: workshops line
verify-html.py 0 problems, node --check script.js clean; competitor checker
unchanged (74 pre-existing Odoo Online hits, 0 added by this diff).
All three bootcamp pages (EN/DE/ES), every buy form: the basket line now
carries info="Q1 2027, English" alongside product_id 12 and qty 1, the
same field shape australia.html uses for location. Value verbatim on all
three pages (the programme is delivered in English in every language).
clear=1 / action / method / target / button labels unchanged.
Verified end to end against the live shop: POST /shop/post_basket returns
303 -> /shop/checkout and /shop/cart shows product 12 at qty 1 with the
info text rendered on the order line.
Deleted odooityourself.html ('delete the site we did for odooityourself').
The URL was already out of sitemap.xml and llms.txt, so no dead URL
stays advertised. The file is removed from the working tree; git history
retains it in full (recoverable from any earlier commit). The two SVGs
it used (images/andrew/ai-agent-on-server.svg and
ai-agent-on-server-mobile.svg) are kept — candidate art for
/odoo-community-ai/ — not part of this removal.
The code may not be named where a visitor or crawler can read it before
opening a reveal panel. Three off-panel leaks removed:
- /odoo-black-friday/ FAQ (visible answer + its FAQPage JSON-LD twin,
byte-identical): 'the code stops working' -> 'the offer stops working'.
- /odoo-hosting-offers/ og:image:alt: the trailing ', and the code
HOST27' dropped; the offer and the 16-30 November window stay.
- /odoo-hosting-offers/ date-scoped Black Friday block: the line no
longer names the code; it states the offer and links to
/odoo-black-friday/ (the only page with a reveal panel), marked with a
dated comment for removal after 30 November 2026.
HOST27 now survives only inside the .bf-panel on /odoo-black-friday/;
/odoo-hosting-offers/ carries zero.
New page "Run Odoo Community with your own AI agent", built from the copywriter's
build-ready copy (copywriter/out/2026-11-01-odoo-community-ai-copy.md) on the newest
page pattern (what-can-i-use-odoo-for/ , odoo-staging-environment/): page-scoped top
bar + language switcher, hero, alternating bands, shared footer, page-scoped CSS only.
Draft contract (all five): <meta name="robots" content="noindex, nofollow">; not added
to sitemap.xml; not added to llms.txt; no inbound link from any page (footer untouched);
self-canonical kept, no og:image. No file outside odoo-community-ai/ is touched.
Copy carried verbatim: title (53), meta (148), one H1, the seven H2s, the FAQ pairs
byte-identical between the visible FAQ and the FAQPage JSON-LD, the two MCP settings
shown apart (a table), the "for your developer" <details> after the offer, and the
fence sentence "does not move data". The copy's DRAFT(R1) marker and its [OPEN-SLOT:…]
marks ship as HTML comments, never as visible text.
The copy's 'Into this page' table, applied as single additive sentences:
- support.html: the 'error 500 after a git check in' FAQ answer links
'test it safely first'.
- blog/posts/speed-run-backup.html: end of the post links
'what a staging environment is, and why you probably do not need one'.
- odoo-hosting-2027.html: one contextual note by the backup/module rows
links 'do you need a staging environment?'.
- index.html: one FAQ card 'Do you need an Odoo staging environment?'.
No footer change. No new impeccable finding category.
New standalone page answering 'odoo staging environment': what staging is,
how odoo.sh's staging branches work (sourced to odoo.sh's own docs and
pricing page), why most small businesses do not need one, and the
back-up-install-test route we offer instead.
- Built on the newest page pattern (top bar, band rhythm, hero, FAQ,
language switcher, footer), page-scoped CSS only; no new framework.
- ICP review R1-R7 applied: backup plan-gate and slot limit stated,
the 'ask us' claim resolved against the Side Hustle limit, the restore
stated as a rollback of live work, the restore routed through support,
the meta de-claims 'one-click ... safely', 'module' defined at first
use, and a scope/price boundary added to the escalation.
- Title 51 chars, meta 148; one H1; six FAQ pairs byte-identical to the
FAQPage JSON-LD; @graph WebPage+Article+FAQPage+BreadcrumbList parses.
- CTA -> support.html#contact (existing route). og:image reuses the
established hero share image.
- sitemap.xml + one llms.txt line; blog/index.json untouched.
Remove the odooityourself.html <url> block from sitemap.xml (42 -> 41 <loc>)
and its line from llms.txt. The page stays published and crawlable: no
noindex, no canonical change, no internal links. It is a deliberate orphan
for a single affiliate, not a search target.
The Bootcamp note comment and its inline [OPEN-SLOT: …] marker named the code
HOST27 in the served HTML, so view-source leaked it before anyone opened the
reveal. Both mark the same still-open decision (BOOTCAMP-CONFIRM / bootcamp BF
price line), so the markers are kept and rewritten without the code string.
The reveal content that names the code on open is untouched.
- delete the whole 'What the code does' <section> (heading, both .bf-lead paragraphs, markup)
- remove HOST27 from meta/og/twitter descriptions, og:image:alt and the WebPage JSON-LD description
- rewrite the two FAQ strings (question + 'On the Rise cost' answer) and their JSON-LD twins so no code is named
- drop the now-dead .bf-lead CSS block and its dangling comment
- reveal mechanic untouched: the code stays inside each native <details> .bf-panel
/odoo-black-friday/ gains og:image + og:image:width/height/alt + twitter:image
pointing at the 1200x630 OG card; /odoo-hosting-offers/ reuses the same card.
Both pages declared no og:image before.
Same base filenames (bf2026-hosting-og, bf2026-hosting-linkedin,
bf2026-bootcamp-lastcall-linkedin), so the pipeline records' image URLs stay
valid. PNGs byte-identical to thumbnail-designer's rendered files:
og 161923B, linkedin 99673B, bootcamp 169572B. WebP siblings regenerated.
The page carried only the Hosting offer; it now carries both Black Friday
offers. Bootcamp box first, then Hosting, in the same <section class="bf-box">,
each in its own .bf-box-inner card, each with the same native
<details class="bf-reveal"> reveal (closed teaser, panel in the DOM in both
states, keyboard-operable, no display:none, JS-off safe, aria-expanded synced
for both). Copy is the copywriter's v3 verbatim: USD 465.00, ten live Thursday
sessions 18:00-19:00 Central Time from Thursday 21 January 2027, the Black
Friday second hosted year, six seats, deadline 30 November 2026. No discounted
Bootcamp figure is printed (HOST27 applicability unconfirmed): the value slot
stays a build placeholder in the one greppable bf-mechanism line. Hero value
statement, meta/og description, WebPage description, FAQ (6 pairs) and the
@graph (second Product+Offer, 465.00 USD) follow the same copy file.
Both pages shipped (355c5d0) with a three-item footer; the site footer is a
four-item commitment. The /odoo-erp-automation-bootcamp/ entry is restored on
both pages, matching index.html / odoo-hosting-2027.html.
/odoo-hosting-offers/: the code HOST27 is added inside the date-scoped Black
Friday block only (BF-BLOCK-START..END), so it disappears with the block after
30 Nov. Title, meta description and H1 are untouched — the intent split holds.
verify-html.py: PROBLEMS 0. node --check script.js: OK.