- Delete the whole "Post a request" section (heading, "Not open yet" note, pick-list/
brief/email form) and the JS+Css that existed only for it: initRequestForm(),
wireRelay("requestFormWrap"…), initRequestForm(), .dir-pickfield/.dir-picklist/.pick.
build-directory.py: drop PICK_START/PICK_END, render_picklist() and its call.
- Rewrite every relay promise to direct contact (trust block, listing note, final CTA);
keep the "we are not a party to the contract" point.
- Trust block: state no fee to the freelancer and no fee to the client.
- Count means client instances: single label "clients on ODOO4projects" (no projects).
- No confirmation gating: comment now states the backend only exposes confirmed profiles.
- Left with a comment, not deleted: ODOO_EXPERTS_QUOTE_WEBHOOK (it gates the dark
anonymous-contact modal, which was out of scope). The modal stays dark (0 buttons).
- Kept: id=1 junk record; the inlined base64 photo (flagged, not changed).
The user's spec (2026-11-03): "remove the ODOO versions. people are lying anyways";
"15 points to distribute between functional system modules integration ai"; "the
languages should be self assessed with 0 can not speak and 5 native. we have the
languages english german and spanish"; "lets limit the focus on the form to 70 chars";
"lets remove the member OCA question … use the OCA link to determine if someone is a
member … show the oca link on top of all links and make it apart with a color, also
dont show the link itself just a button or a chip"; "the only quality signal we can give
and verify is how many projects the freelancer brought to our platform"; "no sorting we
keep the no rank policy"; "get the sort on the page out."
REMOVED (both forms + the filter + the hints):
- `odoo_versions` — the webhook never carried it, so every value was silently dropped
(re-verified: the payload has no `odoo_versions` field).
- `specialities` — replaced by the focus profile.
- the OCA-member question — membership is now DERIVED from `OCA_link`.
ADDED:
- Five focus sliders (functional, system, modules, integration, ai), each 0-5, from ONE
constant (`FOCUS_AXES`), on both forms. Live running total ("12 of 15 points"); the
submit is BLOCKED with an inline warning unless the five total exactly 15. The 15-point
budget of a possible 25 is what stops anyone claiming maximum everywhere, and the form
says so. No-JS: a `<noscript>` block swaps the sliders for five number inputs.
- Three language sliders (English, German, Spanish), each 0-5 (0 = cannot speak,
5 = native), as SEPARATE numeric wire fields — no CSV, no 114-name ISO dataset, no
free text. Preset to 0 (the user's decision, stated): an untouched slider records
"cannot speak" rather than a blank.
- `Focus` capped at 70 characters on both forms with a live counter; the render path
truncates a longer legacy value to 70 with a visible ellipsis.
- The card, in the user's order: photo+name · "N clients on ODOO4projects" (once, with
weight) · Focus · the freelancer's own words · the focus-profile chart (one row per
axis the payload carries, five squares) · the language line ("English 4 · German 5") ·
the links, with the OCA profile chip FIRST and APART (a label, never the raw URL;
"(as declared)") · the anonymous-contact button (JS-only — dark).
- The anonymous-contact modal: sends `{freelancers:[{id,name}], brief, email}` for the
one card, opens only when `ODOO_EXPERTS_QUOTE_WEBHOOK` is set (still empty — ships
dark), and says plainly that the email goes to us, never to the freelancer.
- OCA membership from `OCA_link`, matched on host + PATH PREFIX
(`https://apps.odoo-community.org/community/`), never the bare domain.
ORDER: the client-side `Name` sort is gone from BOTH renderers — the endpoint's order is
rendered as received, so cards reshuffle between loads (the server randomises on purpose).
Nothing is ever ordered by the client count.
Also: the count is rendered ONCE (the payload has one field for it — "clients" and
"projects" are the same number here), `0` renders (a fact) while absent/null renders
nothing, and the legacy `languages`/`specialities` fields are left in the payload but no
longer read.
QC: verify-html.py 0 · check-inline-js OK · test-directory-render.py PASS · competitor QC
exit 3, 74 site-wide pre-existing, 0 in our files · impeccable detect --no-advisory
dir 14 (0 added) / admin 7 (0 added) · 375 px 0 overflow · no form submitted by hand.
Both card renderers emitted an <img> only for a URL, so a record whose Image is an
inline data:image/... URI (what the upload step stores today) fell through to the
monogram. The avatar check now allowlists exactly two schemes — https:// and
data:image/ — in build-directory.py and in the page script; anything else (empty,
junk like "gineoirn", another scheme) still falls back to the monogram, and the
value never reaches src unchecked. The lede copy is corrected to match: a photo
shows where the freelancer gave a link OR uploaded an image.
- Search box: substring, case- and accent-insensitive, over Name, description,
Focus, specialities and language names; combines with every filter (AND).
- Filters: OCA membership (any / member / not a member), Language (canonical
names only), plus Country/Speciality; a project MINIMUM ('N+ projects'),
rendered only when a threshold can narrow the list.
- Languages standardised: a closed ISO 639-1 checklist on both forms, with the
proficiency as a SEPARATE axis (language_levels, 'Spanish:beginner'). A stored
'Spanish - beginners' now maps to the language 'Spanish' and never becomes a
filter option.
- Fix: .dev-card/.dir-filters set display, which beat the UA [hidden] rule, so
filtered-out cards and the JS-off filter bar stayed visible.
- Deterministic order by Name (the endpoint's order alternates between reads).
- Card states plainly that a photo shows only when it is a link (the stored
value is base64 and is not rendered); admin Image field says the same.
- specialities stays free text.
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.