fix(freelancer): the 15-point rule is now one shared constant, in both forms

The edit form's legend read "Skill areas" and carried no directive, while the
application form read "Skill profile — 15 points to distribute" and said what to
do. Same rule, worded in two places — and the edit form lost the sentence.

- New static/focus-rule.js exposes window.O4PFocus: LEGEND, DIRECTIVE, TOTAL, MAX
  and message(sum). Both forms render the legend, the directive and the
  wrong-total message from it, so the two cannot drift apart again.
- Edit form: legend from the constant, the application form's directive under it,
  and the blocked-save message now names the direction.
- Application form: legend set from the constant, live warn and sum gate use the
  same message(); the all-zero gate keeps the directive.
- Message wording: over -> "remove N", under -> "add N", zero -> "place all 15
  points"; the total, aria-live, data-bad, the blocked save and the
  fieldset/legend structure are unchanged.

The rule text now appears once: "You have 17 of 15 — remove 2."
This commit is contained in:
webmaster committed 2026-11-03 18:34:43 +00:00
1 parent 1a1b62136f
commit 768dc1151a
3 files changed
+40 -22

No files matched your search

+9 -4
View File
@@ -146,6 +146,9 @@
} }
</style> </style>
<!-- The 15-point rule, in ONE copy for both forms (this one and the application form). -->
<script src="/static/focus-rule.js"></script>
<!-- 1) Read the credential, then take it OUT of the address bar. <!-- 1) Read the credential, then take it OUT of the address bar.
The hash arrives as ?has=... or ?hash=... (both spellings accepted, 'has' first — the The hash arrives as ?has=... or ?hash=... (both spellings accepted, 'has' first — the
same list the answer page reads, so the two emailed links behave identically). The page same list the answer page reads, so the two emailed links behave identically). The page
@@ -186,7 +189,7 @@
functional: "Functional", system: "System", modules: "Modules", functional: "Functional", system: "System", modules: "Modules",
integration: "Integration", ai: "AI" integration: "Integration", ai: "AI"
}, },
focusTotal: 15, focusTotal: window.O4PFocus.TOTAL,
focusMax: 5, focusMax: 5,
focusMaxChars: 70, focusMaxChars: 70,
descMaxChars: 230, descMaxChars: 230,
@@ -256,6 +259,7 @@
<script> <script>
(function () { (function () {
var C = window.O4E; var C = window.O4E;
var CG = window.O4PFocus; // the shared 15-point rule (static/focus-rule.js)
var app = document.getElementById("app"); var app = document.getElementById("app");
var hash = window.__EDIT_HASH || ""; var hash = window.__EDIT_HASH || "";
var rec = null; // this person's record, once loaded var rec = null; // this person's record, once loaded
@@ -496,7 +500,9 @@
// The five axes. An axis the record does not carry keeps the application form's // The five axes. An axis the record does not carry keeps the application form's
// default of 3, so the block always totals 15 and can be saved. // default of 3, so the block always totals 15 and can be saved.
var axesSet = el("fieldset", ""); var axesSet = el("fieldset", "");
axesSet.appendChild(el("legend", "", "Skill areas")); axesSet.appendChild(el("legend", "", CG.LEGEND));
// The same directive the application form carries, so both forms state the rule.
axesSet.appendChild(el("p", "hint", CG.DIRECTIVE));
C.focusAxes.forEach(function (axis) { C.focusAxes.forEach(function (axis) {
var v = readField(record, axis); var v = readField(record, axis);
if (v === undefined || v === null || v === "") v = 3; // → the block totals 15 if (v === undefined || v === null || v === "") v = 3; // → the block totals 15
@@ -556,8 +562,7 @@
if (focusSum() !== C.focusTotal) { if (focusSum() !== C.focusTotal) {
var sum = focusSum(); var sum = focusSum();
line.className = "status state-err"; line.className = "status state-err";
line.textContent = "The five skill numbers total " + sum + ". They must total exactly " + line.textContent = CG.message(sum); // names the direction: remove / add / place all
C.focusTotal + " before this can be saved.";
focusTotalLine.setAttribute("data-bad", "1"); focusTotalLine.setAttribute("data-bad", "1");
return; return;
} }
+9 -18
View File
@@ -1949,6 +1949,7 @@
</footer> </footer>
<script defer src="/static/lang.js"></script> <script defer src="/static/lang.js"></script>
<script src="/static/focus-rule.js"></script>
<script> <script>
/* ============================================================================ /* ============================================================================
Odoo freelancer directory — page script. Odoo freelancer directory — page script.
@@ -1994,8 +1995,8 @@
// The five self-assessed focus axes — each a SEPARATE numeric field (0-5) on the // The five self-assessed focus axes — each a SEPARATE numeric field (0-5) on the
// wire, no CSV, no encoding. They must total exactly FOCUS_TOTAL. // wire, no CSV, no encoding. They must total exactly FOCUS_TOTAL.
var FOCUS_AXES = ["functional", "system", "modules", "integration", "ai"]; var FOCUS_AXES = ["functional", "system", "modules", "integration", "ai"];
var FOCUS_TOTAL = 15; var FOCUS_TOTAL = O4PFocus.TOTAL; // the shared rule (static/focus-rule.js)
var FOCUS_MAX = 5; var FOCUS_MAX = O4PFocus.MAX;
var FOCUS_AXIS_LABELS = { var FOCUS_AXIS_LABELS = {
functional: "Functional", system: "System", modules: "Modules", functional: "Functional", system: "System", modules: "Modules",
integration: "Integration", ai: "AI" integration: "Integration", ai: "AI"
@@ -2089,22 +2090,17 @@
savable — an all-zero block is simply left unsent). */ savable — an all-zero block is simply left unsent). */
function focusError(form, required) { function focusError(form, required) {
var sum = focusSum(form); var sum = focusSum(form);
if (sum === 0) { if (sum === 0) return required ? O4PFocus.DIRECTIVE : "";
return required return O4PFocus.message(sum); // "" when right; names the direction when not
? "Distribute all 15 points across the five areas — they cannot all be 0."
: "";
}
if (sum !== FOCUS_TOTAL) {
return "Your five focus numbers total " + sum + ". They must total exactly " +
FOCUS_TOTAL + ".";
}
return "";
} }
/* Wire one form's sliders: five <input type=range> with an adjacent <output>, a /* Wire one form's sliders: five <input type=range> with an adjacent <output>, a
live running total, and a live warning while the number is wrong. */ live running total, and a live warning while the number is wrong. */
function initFocusUI(form, warnId, totalId) { function initFocusUI(form, warnId, totalId) {
if (!form) return; if (!form) return;
// The legend text comes from the shared rule (static/focus-rule.js), not the markup.
var legendEl = form.querySelector(".focus-field legend");
if (legendEl) legendEl.textContent = O4PFocus.LEGEND;
var wraps = form.querySelectorAll(".focus-field"); var wraps = form.querySelectorAll(".focus-field");
Array.prototype.forEach.call(wraps, function (w) { Array.prototype.forEach.call(wraps, function (w) {
var hidden = w.querySelector(".focus-sliders"); var hidden = w.querySelector(".focus-sliders");
@@ -2125,12 +2121,7 @@
el.setAttribute("aria-valuetext", n + " of " + FOCUS_MAX); el.setAttribute("aria-valuetext", n + " of " + FOCUS_MAX);
}); });
if (total) total.textContent = sum + " of " + FOCUS_TOTAL + " points"; if (total) total.textContent = sum + " of " + FOCUS_TOTAL + " points";
if (warn) { if (warn) warn.textContent = O4PFocus.message(sum);
warn.textContent = sum === FOCUS_TOTAL
? ""
: (sum === 0 ? "You have 0 of 15 — place all 15 points."
: "You have " + sum + " of 15. The five must total exactly 15.");
}
} }
Array.prototype.forEach.call(form.querySelectorAll(".focus-field input"), function (el) { Array.prototype.forEach.call(form.querySelectorAll(".focus-field input"), function (el) {
el.addEventListener("input", refresh); el.addEventListener("input", refresh);
+22
View File
@@ -0,0 +1,22 @@
/* The ONE source of truth for the "skill profile" rule — the 15-point budget.
Two forms render this rule: the directory application form
(/odoo-community-developers/) and the private self-service edit form
(/freelancer-edit/). Both take the legend, the directive and the wrong-total
message from here, so the two cannot word the same rule differently again.
Loaded before each page's own script. */
(function () {
var TOTAL = 15;
window.O4PFocus = {
TOTAL: TOTAL,
MAX: 5,
LEGEND: "Skill profile — 15 points to distribute",
DIRECTIVE: "Distribute all 15 points across the five areas — they cannot all be 0.",
/* The wrong-total message, naming the direction to move. "" when the sum is right. */
message: function (sum) {
if (sum === TOTAL) return "";
if (sum === 0) return "You have 0 of " + TOTAL + " — place all " + TOTAL + " points.";
if (sum > TOTAL) return "You have " + sum + " of " + TOTAL + " — remove " + (sum - TOTAL) + ".";
return "You have " + sum + " of " + TOTAL + " — add " + (TOTAL - sum) + ".";
}
};
})();