A record saved after the last build never reached the page: the list was
build-time HTML only, so the new freelancer (id=2) stayed invisible until
someone re-ran the build. That is the cause, not the symptom.
- The page now GETs the odoo_experts webhook on load and re-renders the cards
and the filter bar from what it returns.
- The build-time HTML stays in the page and is the fallback: it is replaced
only when the fetch succeeds, so JavaScript-off and webhook-down still show
the last built list. An empty response never overwrites a populated list.
- The renderer mirrors build-directory.py field for field (casing aliases
included); every freelancer-written value goes in as textContent.
- initFilters() is split so one delegated listener re-reads the selects on
every change: the same code filters the built list and the rendered one.
- Confirmation is NOT gated: the endpoint carries no status/email_verified
marker, so nothing was guessed. Recorded in the page comment as a data gap.
- rebuild: build-directory.py, so the served HTML starts from the real set.
Country was free text, so one country could be entered as Germany / DE /
Deutschland / germany and split the directory filter four ways. Both the
application and the admin form now submit from a native <select> of the
249 ISO 3166-1 alpha-2 codes: the option shows the English name, the value
is the code. No free-text fallback and no "other" escape hatch. Required,
with an empty "— choose —" first option. The directory filter shows the
English name for the stored code, so one country is one option.
Non-trivial, non-UI: the submitted value format lives in one constant per
page, COUNTRY_VALUE_FORMAT ("code" | "name") — switching to full country
names is a one-line change if the n8n workflow expects names, not codes.
The webhook field name stays exactly "Country".
The keyword work found no trust keyword with demand and none we could answer
without a review/vetting claim, so the trust content ships as a block on the
directory page itself. It folds the old "How this works" section and states
our limits plainly: you deal with the freelancer, we rank nobody, we verify
nothing (declarations tagged '(as declared)'), invitations carry a one-click
opt-out. The contract point links to the note under the listings instead of
copying it. No trust adjective is a claim; no review/rating/star.
The two relay success strings still described a delivery the stub endpoints
cannot perform ("on its way", "we will email them one invitation"). They were
unreachable only while ODOO_EXPERTS_QUOTE_WEBHOOK / ODOO_EXPERTS_INVITE_WEBHOOK
are empty — setting either re-armed the F1 false confirmation.
- request form success -> "we have your request … come back to you by email"
- invite form success -> "we have recorded your invitation … one-click opt-out"
- comment at each constant: setting it switches the form on; the success copy
must already be a receipt.
Constants stay empty; both forms stay dark.
- F2: 'We publish exactly what you write' -> 'We publish what you write —
after we check it: your photo, your email address and for spam' (the page's
own consent line + photo check + spam gate made the absolute false)
- claims sweep: drop the 'no claim we cannot back' meta-claim and the
'not through an agency' absolute; state the individual rule as the
declaration we ask for, and make the card's individual tag read
'(as declared)' to match the OCA tag
- F3: directory lede now says the individual rule is a declaration, not a fact
- F4: 'How this works' 110 -> 44 words (three short lines, no relay promise)
- F5: count framed as 'a number we recorded' (owner-typed, not derived);
photo hint agrees with its '(optional)' label; kicker 'ODOO COMMUNITY
FREELANCERS' -> 'ODOO FREELANCERS' and the meta/og drop 'Odoo Community'
so an Enterprise client is not invited to self-exclude
- F1: the request and invite forms SHIP DARK — their endpoint constants are
empty and the forms stay hidden behind a plain 'not open yet' line
(no submit, so no false confirmation); the invite copy carries the
one-click opt-out line the ICP asked for before the form returns
The new '#request' link in the closing CTA was inheriting the site-wide brass-deep
link colour on the midnight background (3.6:1). It uses brass-bright now, so the
page holds its zero-added impeccable profile (14, same as before the change).
- languages/specialities now POST as comma-separated STRINGS ("English,German"),
not arrays; the build script splits them into separate filter options and tags.
- the client request form is no longer inert: it posts to odoo_experts_quote with
an explicit freelancer pick-list (never an implicit all) and a client address.
- new invite-a-friend form posts to odoo_experts_invite_friend (sender name +
friend email); we send the invitation, not the sender's address.
- both new forms show success only on a returned 200 (n8n answers 200 before the
workflow runs) and promise no timing.
- build-directory.py: render the pick-list, fix the country option casing.
- pages stay drafts (noindex, not in sitemap/llms, no inbound links).
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.