سجل التغييرات

GitHub

كل تغيير جدير بالذكر، إصدارًا بعد إصدار. الملف نفسه يأتي مع المستودع ومترجم إلى جميع اللغات الـ19.

[1.90.0] — 2026-07-04

Mock Interview 2.0 (Epic 15). تحوّل صفحة #/mock-interview الجديدة سيرتك الذاتية وملفك الشخصي والـ two-pager وبنك القصص إلى تدريب مقابلة دوراً بدور:

  • تدريب حواري — حدّد الدور المستهدف (+ الشركة / الوصف الوظيفي اختيارياً) ويفتتح المُحاوِر بسؤال مُركَّز. تحصل كل إجابة تُرسلها على ردّ منظَّم: Feedback (نقاط القوة + الفجوة بمنهج STAR+R)، وScore (N/5)، وNext question يسبر أضعف جزء في إجابتك الأخيرة. كل ذلك مبني على مستندك الحقيقي في الخادم — ولا يختلق أبداً خبرة لا تملكها.
  • واعٍ ببنك القصص — يُضمَّن interview-prep/story-bank.md في المطالبة (بنفس مستوى الثقة الممنوح لـ cv.md) كي تشير الملاحظات إلى أفضل قصصك.
  • مباشر أو يدوي — مع وجود مفتاح مزوّد، يُنفَّذ الدور مباشرةً عبر السلسلة المشتركة (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models)؛ ومن دون مفتاح تحصل على مطالبة جاهزة للنسخ واللصق (احتياطي أمين، بلا إجابات مختلقة).
  • جلسات محفوظة — انقر حفظ النص للاحتفاظ بمقابلة منتهية في طبقة المستخدم (interview-prep/mock-{company}-{role}-{date}.md)؛ وتعرض الصفحة الجلسات المحفوظة وتفتحها وتحذفها.

جديد: server/lib/routes/interview.mjs (وحدة المسار الثامنة عشرة)، public/js/views/mock-interview.js، server/lib/llm-dispatch.mjs (سلسلة المزوّدين المشتركة)، PATHS.storyBank، bundleProjectContext({ extraFiles }). 30 مفتاح i18n جديد في كل 16 لغة. الاختبارات: tests/interview-routes.test.mjs.

[1.89.0] — 2026-07-04

ملاءمة المرشّح للسوق — الـ two-pager (Epic 14). تتيح لك صفحة #/two-pager الجديدة تدوين ما تريده أنت فعلاً من دورك المهني القادم، على غرار «Mnookin two-pager» من كتاب Never Search Alone:

  • مُنشئ موجَّه — سردية بصيغة المتكلّم «من أنا»، وملاحظة «البيئة المستهدفة»، وخمسة محرّرات لقوائم الرقائق: loves وmust-haves وhates وdeal-breakers وnon-negotiables. تُحفظ في طبقة المستخدم للمشروع الأب (config/two-pager.yml) عبر PUT /api/two-pager — ولا تُستبدل أبداً بتحديثات النظام.
  • مساعد التعبئة بالذكاء الاصطناعي (POST /api/two-pager/draft) — يبني موجّه Mnookin جاهزاً للتشغيل مع تضمين سيرتك الذاتية + ملفك الشخصي، لتشغّله في أي نموذج لغوي وتلصق النتيجة مرة أخرى. يستخدم موادك الخاصة فقط؛ ولا يُختلق شيء.
  • شارة الملاءمة-لما-تريد — يُظهر كل إعلان على #/scan الآن درجة ملاءمة ◎ N (من جانب العميل، عبر window.FitScore) تقارن نوع العمل والبلد والحد الأدنى للراتب والانتقال في الإعلان مع الـ two-pager الخاص بك. صادقة بحكم التصميم: عندما لا يقدّم الإعلان إشارة قابلة للمطابقة، لا تُعرض أي شارة (ولا رقم مُختلق أبداً). تُوزَن انتهاكات deal-breakers أكثر من الكراهيات الخفيفة.
  • تُغذّي كل تقييم — يُضمَّن الـ two-pager المحفوظ في bundleProjectContext، بحيث تمزج جميع تقييمات النماذج اللغوية اللاحقة تفضيلاتك المعلَنة مع مطابقة السيرة الذاتية مقابل الوصف الوظيفي.

جديد: server/lib/routes/two-pager.mjs، public/js/views/two-pager.js، public/js/lib/fit-score.js، PATHS.twoPager. 27 مفتاح i18n جديد عبر جميع الـ 16 لغة. الاختبارات: tests/two-pager-routes.test.mjs، tests/fit-score.test.mjs.

[1.88.0] — 2026-07-04

تحسينات المشكلة #29 — فجوات i18n في المسح + نظافة الـ API.

  • تعريب آخر سلاسل المسح المضمّنة بشكل ثابت (خارطة الطريق v1.69.4): تمرّ الآن حبوب ملخّص المصدر (N جديدة / M مطابقة)، وتنبيهات N وظائف جديدة، وشارة reloc عبر t() — 4 مفاتيح جديدة (scan.pillNew، scan.pillMatching، scan.newOffers، scan.relocBadge) عبر جميع الـ 16 لغة. لم يعد المستخدمون غير الناطقين بالإنجليزية يرون نصاً إنجليزياً متناثراً في مسار المسح الأساسي.
  • تعطيل ترويسة X-Powered-By (خارطة الطريق v1.69.5): app.disable('x-powered-by') في createApp() — لم يعد الخادم يُعلن عن Express. (بقيّة تلك الملحمة سبق تسليمها: يزيل parentVersion تعليق release-please الخاص به، ومبدّل السمة في الوضع الفاتح، وإغلاق النوافذ المنبثقة عند تغيير المسار، وتعريب «Score» (rep.score) في التقارير.)

الاختبارات: tests/scan-i18n-gaps.test.mjs + تأكيد غياب X-Powered-By في tests/security-headers.test.mjs.

[1.87.0] — 2026-07-04

4 مزوّدي مسح جدد بلا مصادقة (تكافؤ مع career-ops الأصل v1.16.0). يكبر سجل الماسح من 41 → 45 محوّلاً (40 EN + 5 RU) — جميعها عامة، بلا مصادقة، مثبّتة على المضيف، redirect:'error' (آمنة من SSRF)، ولكل منها اختبار معزول لـ CI:

  • Get on Board (getonbrd) — JSON:API عام على مستوى البوابة (تقنية LATAM/عن بُعد)، يُختار بحسب المزوّد، مع ترقيم صفحات. server/lib/sources/getonbrd.mjs.
  • Amazon (amazon) — JSON بحث عام من amazon.jobs، يُكتشف بحسب المضيف أو provider: amazon، مع ترقيم بالإزاحة. server/lib/sources/amazon.mjs.
  • Avature (avature) — نظام ATS من *.avature.net لكل مستأجر، يُحلَّل من HTML، يُكتشف بحسب المضيف أو provider: avature. server/lib/sources/avature.mjs.
  • SAP SuccessFactors (successfactors) — قائمة بلاطات RMK لكل مستأجر (*.successfactors.eu/.com، jobs2web.com)، تُحلَّل من HTML. server/lib/sources/successfactors.mjs.

يوفّر كلٌّ منها sources/<slug>.mjs (مع meta مُكتشف تلقائياً → قائمة #/scan المنسدلة) و portals/adapters/<slug>.mjs ضمن ALL_ADAPTERS (قاعدة السجلّين) + tests/sources-<slug>.test.mjs. ارتفع عدّ ALL_ADAPTERS وكذلك تأكيدات المعرّفات المرتّبة ومجموعة EN في /api/scan/sources من 36→40؛ ويسرد GET /api/scan/sources الآن 45.

[1.86.0] — 2026-07-03

إحصاءات حسب الأدوار المستهدفة (#/stats) — إحصاءات سوق الوظائف والرواتب لأدوارك المستهدفة. تقرأ صفحة تحليلات جديدة أدوارك المستهدفة من الملف الشخصي (config/profile.yml → غير مضمّنة بشكل ثابت) والوظائف من آخر عملية مسح، ثم تعرض لكل دور وبلد:

  • الوظائف حسب البلد ومتوسط الراتب حسب البلد (USD) — مجمَّعة على جانب العميل (public/js/lib/role-stats.js، مع إعادة استخدام window.Countries) من البيانات المتفرّقة التي تجمعها الماسحات بالفعل. تُطبَّع الرواتب بأي عملة إلى USD عبر جدول FX تقريبي بشكل صريح، مع تنبيه بشأن حجم العينة — ولا تُلفَّق أبداً.
  • مرشِّحات الدور والبلد ومخططات أعمدة واتجاه مضمّنة بصيغة SVG مكتوبة يدوياً (بلا اعتماديات جديدة، آمنة لـ CSP — تستخدم addEventListener فقط).
  • حفظ لقطة (POST /api/stats/snapshot) يحفظ التجميع الحالي في data/role-stats.jsonl؛ ويتتبّع مخطط الاتجاه (GET /api/stats/trend) أعداد الوظائف عبر الزمن — عرض «الديناميكية». هجين صادق: تأتي اللقطات من بيانات مسح محلية، وتُحدَّث عند الطلب.
  • مترجَمة بالكامل في جميع الـ 16 لغة (26 مفتاح i18n جديد).

جديد: server/lib/routes/stats.mjs (وحدة المسارات الـ 16)، وpublic/js/lib/role-stats.js، وpublic/js/views/stats.js، وPATHS.roleStats؛ اختبارات role-stats.test.mjs (7) + stats-routes.test.mjs (5).

[1.85.0] — 2026-07-03

اللغات الألمانية والإيطالية والتركية (تكافؤ اللغات مع المشروع الأصلي career-ops v1.16.0). أصبحت الواجهة الآن متوفّرة بـ 16 لغة — تنضمّ de 🇩🇪 وit 🇮🇹 وtr 🇹🇷 إلى الـ 13 لغة القائمة.

  • ترجمة كاملة للواجهة — تُرجمت جميع مفاتيح i18n الـ 730 في public/js/lib/locales/i18n-dict.{de,it,tr}.js؛ ويسرد مبدّل اللغة Deutsch / Italiano / Türkçe، ويتعرّف الاكتشاف التلقائي للغة المتصفّح على de/it/tr (public/js/lib/i18n.js).
  • دليل المساعدة داخل التطبيق — تُرجمت docs/help/{de,it,tr}.md (البنية الكاملة 19 H2 / 75 H3)، ويقدّمها GET /api/help/:lang.
  • التوثيق — أُضيفت README.{de,it,tr}.md وCHANGELOG.{de,it,tr}.md؛ وتغطّي الآن بوابة تكافؤ لغات CHANGELOG 15 لغة غير EN.
  • سقالة المطالبات — عُرّبت server/lib/prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) للّغات الثلاث الجديدة، بحيث يتبع مخرج الـ LLM لغة الواجهة.

وُسِّعت جميع بوابات التكافؤ (i18n-locale-files وi18n-coverage وcheck-changelog-parity وlang-switcher-rtl) لتشمل مجموعة الـ 16 لغة.

[1.84.0] — 2026-06-30

cooldown إعادة التقديم + التعويض في pipeline.md (تكافؤ مع المشروع الأصلي career-ops v1.15.0). ترقيتان للماسح:

  • cooldown إعادة التقديم (#1201): يتخطّى مسح EN الآن الأدوار لدى الشركات التي تقدّمت إليها مؤخراً، لتبقى النتائج مُركّزة على الفرص الجديدة. هيّئ نوافذ لكل شركة في config/profile.yml تحت مفتاح re_apply_windows: (last_apply_date وsame_role_days وapplied_to: [roles] ومفتاح cross_role_bucket الاختياري)؛ مطابقة الشركات غير حساسة للترقيم + مبنية على حدود الكلمات (server/lib/cooldown.mjs). معطّلة عند غياب المفتاح؛ ويعرض سجل الفحص Cooldown skipped: N.
  • التعويض في pipeline.md (#1017): تحفظ العروض الممسوحة الآن راتبها في عمود trailing اختياري (url | <salary>) في data/pipeline.md. يبقى الرابط مفتاح إزالة التكرار (يُحذف عمود | comp عند القراءة)، وتُطهَّر قيمة الخلية (دون حقن صفوف/أعمدة وتُبطَّل الصيغ البادئة)، وتبقى ملفات pipeline المحتوية على روابط فقط متوافقة مع الإصدارات السابقة. يشحن مع tests/cooldown.test.mjs + اختبارات تعويض pipeline. عدد المصادر ثابت عند 41 (كلاهما ترقيات لمنطق الفحص، ليستا لوحتَين جديدتَين).

[1.83.0] — 2026-06-30

كاشف إعادة النشر / الوظائف الوهمية (تكافؤ مع المشروع الأصلي career-ops v1.15.0). لوحة جديدة 🔁 الوظائف المُعاد نشرها / الوهمية في #/scan تُحدّد مجموعات الشركة+الوظيفة التي أُعيد إدراجها تحت روابط URL مختلفة خلال نافذة متحرّكة مدتها 90 يوماً — إشارة إلى خطوط توظيف قديمة ووظائف وهمية. يستند إلى مطابق ضبابي لعناوين الوظائف (server/lib/role-matcher.mjs) وكاشف للقراءة فقط (server/lib/detect-reposts.mjs) يعمل على data/scan-history.tsv، ويُعرض عبر GET /api/scan/reposts. كذلك: يُبلّغ parentVersion في /api/health الآن عن رقم semver فقط (يُحذف تعليق release-please # x-release-please-version). يشحن مع tests/detect-reposts.test.mjs. عدد المصادر ثابت عند 41 — الكشف عن إعادة النشر ميزة تحليلية وليس لوحة جديدة.

[1.82.0] — 2026-06-30

مصدر فحص NoDesk (تكافؤ مع المشروع الأصلي career-ops v1.15.0). أصبحت تغذية RSS للوظائف عن بُعد على مستوى اللوحة بأكملها من NoDesk مصدر فحص من الدرجة الأولى — أضف إدخال provider: nodesk ليظهر في قائمة Source المنسدلة في #/scan (41 محوّلًا إجمالًا: 36 إنجليزي + 5 روسي). المضيف مثبّت على nodesk.co مع redirect:'error' (حماية من SSRF)؛ وتُقسَّم العناوين على Role at Company (لا تحتوي NoDesk على وسم للموقع، لذا يبقى الموقع فارغاً)؛ وجميع الصفوف عن بُعد. يشحن مع مجموعة اختبار tests/sources-nodesk.test.mjs معزولة CI؛ مجموعة الاختبارات الوحدوية الكاملة على الأخضر عند 1523.

[1.81.0] — 2026-06-29

تكافؤ مع المشروع الأصلي career-ops — 13 مصدر فحص وظائف جديد. ينقل أحدث دُفعة مزوّدين من main في Fighter90/career-ops إلى الماسح الداخلي للعملية. واجهات API عامة على مستوى اللوحة بأكملها (مُختارة بالمزوّد): Arbeitnow وHimalayas وJobicy وLanding.jobs و4 Day Week وThe Muse وThe Hub وJobspresso (RSS) وHacker News “Who is hiring?” (خطوتان عبر Algolia). لوحات بولندية (تُكتشف عبر المضيف أو provider:): JustJoin.it وNoFluffJobs (بحث POST). ATS لكل مستأجر (يُكتشف تلقائياً من careers_url): Pinpoint (<slug>.pinpointhq.com/postings.json) وRippling (ats.rippling.com/<slug> → لوحة api.rippling.com). كل مصدر مثبّت بالمضيف مع redirect:'error' (حماية من SSRF) وقابل للاختيار في قائمة Source المنسدلة في #/scan — يضم السجل الآن 40 محوّل ماسح (35 إنجليزي + 5 روسي). يضيف 13 مجموعة اختبار معزولة CI لكل مصدر؛ مجموعة الاختبارات الوحدوية الكاملة على الأخضر عند 1513.

[1.80.0] — 2026-06-28

خمسة تحسينات للمسح (أفكار من job-crawler، أُعيدت كتابتها). (1) مصدر Teamtailor — مواقع <slug>.teamtailor.com لكل مستأجر عبر تغذية /jobs.rss العامة، يُكتشف تلقائياً من careers_url (المضيف مثبّت + redirect:'error')؛ يضم السجل الآن 27 محوّلاً. (2) حجر المصادر — المصدر الذي يعيد 404/410 دائمًا يُسجَّل في data/scan-quarantine.json ويُتخطّى في عمليات المسح اللاحقة (إصلاح ذاتي: يُعاد بعد 14 يومًا). (3) الحد لكل مصدر — حقل اختياري في #/scan يحدّ من عدد الوظائف لكل board (∞ افتراضيًا). (4) نُشر خلال — مرشّح عُمر من جهة العميل (24 ساعة / 7 أيام / 30 يومًا). (5) عمليات البحث المحفوظة + ★ المفضلة — سمِّ مجموعات المرشّحات وأعد استخدامها وميّز الوظائف؛ تُحفظ في localStorage مع تحقّق دفاعي (تُعاد تهيئة الذاكرة المؤقتة التالفة بنظافة)؛ وتُعاد تهيئة ذاكرة النتائج قبل كل مسح وتُملأ مباشرةً.

[1.79.0] — 2026-06-28

مصدر فحص WeWorkRemotely (تطابق مع career-ops v1.14.0). أصبحت تغذية RSS للوظائف عن بُعد على مستوى اللوحة من We Work Remotely مصدر فحص من الدرجة الأولى — أضف إدخال provider: weworkremotely ليظهر في قائمة Source في #/scan (26 محوّلًا إجمالًا). المضيف مثبّت على weworkremotely.com مع redirect:'error' (حماية من SSRF)؛ وتُقسَّم العناوين على Company: Role. كذلك تُقتطع كلمات title_filter المفتاحية قبل فحص الطول (parent #1261).

[1.78.2] — 2026-06-27

تحسين i18n وتجربة الاستخدام (متابعات لـ v1.78.1). أصبح الاسم الوصفي للشعار مترجماً في جميع اللغات الـ 13 (nav.logoHome). الضغط على Enter في البحث العام أثناء وجودك في #/scan يفرض إعادة تصيير كي لا يُفقد المصطلح المُعبّأ مسبقاً (حارس المسار نفسه). تُرجم health.title إلى البولندية (Kondycja) والدنماركية (Systemtilstand) — كان سابقاً بالإنجليزية. الاختبارات 1235 → 1238.

[1.78.1] — 2026-06-27

إصلاحات تجربة Scan. يتحدّث جدول نتائج #/scan الآن تلقائياً أثناء الفحص ومرة أخرى عند انتهائه — دون إعادة تحميل يدوية. يعرض البحث العام في الشريط العلوي تلميح Enter، وعند إدخال استعلام ليس رابطاً ينتقل إلى #/scan مع تعبئة حقل البحث مسبقاً (كان #/tracker). أصبح الشعار الآن رابطاً إلى لوحة التحكم (الصفحة الرئيسية).

[1.78.0] — 2026-06-27

مرشّح جغرافي في صفحة Scan — فلترة النتائج حسب الدولة، مع الأعلام. قائمة الدولة الجديدة في #/scan تعرض كل دولة مكتشفة في نتائجك (رمز العلم + العدد)، لتُبقي فقط الوظائف المرتبطة بدولة معينة — إلى جانب مرشّح Remote/Hybrid/Onsite، حتى تبحث عن العمل المرتبط بدولة والعمل عن بُعد معاً. مدعوم بمساعد جديد countries.js يربط موقع الإعلان النصي الحر (أسماء الدول والمرادفات و~100 مدينة رئيسية) بدولة ISO + علم؛ والاكتشاف متحفظ ولا يخمّن أبداً.

[1.77.0] — 2026-06-27

إضافة الدنماركية (Dansk) كلغة الواجهة الثالثة عشرة. ترجمة كاملة لواجهة المستخدم ودليل المساعدة المدمج (19 H2 / 75 H3) وREADME وCHANGELOG. تنضم الدنماركية إلى منتقي اللغات بالأعلام؛ وتغطي الآن منظومة i18n (المُجمِّع، التدقيق، بوابات التكافؤ، اللقطة) 13 لغة.

[1.76.0] — 2026-06-26

تطابق مع career-ops v1.13.0 الأصلي — ست مصادر وظائف جديدة، وتقوية الماسح، وجدول نتائج بلا حد أقصى.

أُضيف

  • ست مصادر ATS لكل مستأجر — BambooHR وBreezy HR وComeet وPersonio وRecruitee وSolidJobs. تُكتشف تلقائيًا من مضيف careers_url (يتطلب Comeet عنوان api: كاملًا)؛ ويُثبَّت كل مضيف بنمط regex مُرسَّخ + redirect:'error' (حماية من SSRF). قابلة للاختيار في قائمة Source في #/scan — يضم السجل الآن 25 محوّلًا (20 إنجليزي + 5 روسي). يضيف مساعد fetchText لتغذية Personio بصيغة XML.
  • trust_filter — تقييم ثقة اختياري (0–100، مستوى high/medium/low، أعلام)، للتعليق فقط. الصفوف دون high تحصل على شارة ⚠ محايدة لغويًا في #/scan؛ ولا تُستبعد أي وظيفة أبدًا.
  • Arbeitsagentur remoteMatch + remoteMaxPages — اكتشاف العمل عن بُعد عبر الإعداد: title أو filter (homeoffice=nv_true من جهة الخادم + ترقيم الصفحات) أو off.

تغيّر

  • لا حد لنتائج المسح. أُزيل حد العرض MAX_STORED_RESULTS (2000) — تُخزَّن كل النتائج المطابقة ويُصفِّحها جدول #/scan (200/صفحة).
  • متانة مرشّح العنوان — الاختصارات القصيرة (COO، SDR…) تُطابَق على حدود الكلمات؛ ولم يعد إعداد title_filter المعطوب يُعطّل المسح. كلا الماسحَين.

اختبارات

  • +32 حالة (1190 → 1222): sources-ats-providers وtitle-filter وarbeitsagentur-remote وtrust-validator وحارس scan-result-cap المُعاد كتابته («بلا حد»).

[1.75.2] — 2026-06-19

docs: تكافؤ توثيقي كامل لمُجمِّعات الماسح في الإصدار v1.75.0 عبر جميع اللغات الـ 12. دون تغيير في الكود — يوائم التوثيق المُوجَّه للمستخدم مع المصادر السبعة التي وصلت في الإصدار v1.75.0:

  • دليل المساعدة (12 لغة). يكتسب §5 كتلة content_filter (تبويب عبر كلمات مفتاحية للوصف/المقتطف، نظير location_filter) وملاحظة عن المُجمِّعات؛ ويُدرج §7 المصادر السبعة الجديدة في جولة الفحص بنقرة واحدة وفي التعداد الكامل للقائمة المنسدلة Source؛ ويُصحَّح عدد المحوّلات في §17 من القيمة القديمة “11 adapters” إلى “19 adapters — 14 English + 5 Russian”. لم يُضَف أي عنوان ##/###، لذا تبقى بنية 19 H2 / 75 H3 المُقفلة دون تغيير.
  • README (9 لغات كاملة). نقطة جديدة “Aggregator boards (v1.75.0)” أسفل مصادر الفحص، إضافةً إلى شارة الإصدار المرفوعة إلى v1.75.2. (ملفات README المختصرة لـ pl/uk/ar ليس لها قائمة لكل مصدر، وهي متروكة عمدًا دون تغيير هناك.)
  • وثائق مرجعية. يكتسب docs/portals-examples.md قسمًا جاهزًا للنسخ واللصق بعنوان “Aggregator boards” يضم كتل تهيئة provider: / <provider>: دقيقة للمصادر السبعة جميعًا؛ ويُحدَّث docs/PROJECT.md إلى 19 adapters؛ ويوثّق docs/sdd/CONVENTIONS.md التمييز بين السجلّين (sources/registry.mjs للقائمة المنسدلة مقابل portals/registry.mjs للجلب)، واختيار المُجمِّع المبني على provider: المُمرَّر بوصفه opts.company، ومُطهِّر كتابة الفحص (scan-sanitize.mjs)، وعدد اختبارات الإصدار v1.75.1 (1190).
  • QA. أُضيف qa/QA-REGRESSION-PROMPT-v1.75.2-FULL.md — مُشغِّل بوابة الإصدار للسطح الكامل، مُحدَّث لدورة مُجمِّعات الفحص في الإصدار v1.75.x.

[1.75.1] — 2026-06-19

fix(scan): صقل المتانة على المصادر المُدارة بالإعدادات في الإصدار v1.75.0. ثلاثة إصلاحات تعزيز صغيرة من المراجعة بعد الإصدار (دون تغيير في السلوك لأي فحص سليم):

  • تأخيرات ترقيم الصفحات المراعية للإلغاء. أصبحت فترات التهذيب بين الصفحات لكل من Glints (300 ms) وJobstreet/SEEK (200 ms) تُحَلّ فورًا عند إطلاق AbortSignal الخاص بالفحص، عبر مساعد جديد delay(ms, signal) في server/lib/http-json.mjs، بحيث لا يستطيع عميل منقطع إبقاء فحص قيد الترقيم مفتوحًا لفترة توقف إضافية.
  • خطأ وصفي عند الاستجابة غير المنسّقة بـ JSON. أصبح fetchJson يغلّف جسم استجابة 2xx غير المنسّق بـ JSON (مثل صفحة صيانة HTML تُقدَّم بالحالة 200) على هيئة non-JSON 2xx response from <url> بدلًا من إظهار SyntaxError مجرّد، بحيث يُسمّي سجل أخطاء الماسح لكل مصدر نقطة النهاية المُختلّة السلوك.
  • تطبيع أقوى لكتابة الفحص. أصبح normalizeScanScalar يطوي علامة الجدولة الرأسية وتغذية الصفحة وفاصلَي السطر/الفقرة في Unicode (\v \f U+2028 U+2029) إضافةً إلى \r \n \t — وهي مجموعة فائقة صارمة، بحيث لا يبقى أي فاصل سجل/سطر قد تعتمده جدول بيانات أو عارض حتى يصل إلى scan-history.tsv.

[1.75.0] — 2026-06-19

feat(scan): نقل التكافؤ مع المشروع الأصلي career-ops v1.12.0 — سبعة مصادر وظائف جديدة، وتصفية المحتوى، وإصلاحات أمنية/جودة. تشغّل web-ui ماسحاتها الخاصة داخل العملية (فهي لا تستدعي scan.mjs الخاص بالمشروع الأصلي عبر shell out)، لذا لا تتدفق تغييرات المزوّد والمسح في الإصدار الأصلي v1.12.0 تلقائيًا — يعيد هذا الإصدار تنفيذ المنطبق منها وفق عقد المحوّلات في web-ui.

  • سبعة مصادر ماسح جديدة. ثلاثة مُجمِّعات للعمل عن بُعد على مستوى اللوحة بأكملها — RemoteOK وRemotive وWorking Nomads — تندرج ضمن النمط المُكتشَف تلقائيًا server/lib/sources/*.mjs (تُختار عبر provider: remoteok / remotive / workingnomads). وأربعة مُجمِّعات إقليمية مُوجَّهة بالتهيئة — careers الخاص بـ IBM وArbeitsagentur (وكالة العمل الاتحادية الألمانية) وGlints (جنوب شرق آسيا) وJobstreet / SEEK — تقرأ كتلة تهيئة <provider>: لكل مُدخَل؛ ويمرّر en-scanner الآن مُدخَل الشركة المُحلَّل إلى كل جالب (fetcher) كي تتمكن من قراءته. تظهر السبعة جميعها تلقائيًا في القائمة المنسدلة للمصادر في #/scan.
  • content_filter (المشروع الأصلي #974). كتلة اختيارية في portals.yml (قائمتا كلمات مفتاحية positive / negative) تُبوِّب الإعلان بناءً على نص وصفه/مقتطفه — تحاكي دلالات location_filter؛ والإعلانات بلا وصف تمرّ دائمًا. مُوصَّلة بكلا الماسحين EN وRU.
  • تقوية كتابة المسح (المشروع الأصلي #1098). تُطهَّر الآن بيانات تعريف الموجزات الخارجية قبل أن تستقر في data/scan-history.tsv وdata/pipeline.md: تُطوى أحرف التحكم (لم يعد بإمكان سطر جديد في اسم الشركة/المسمّى حقن صف TSV)، ويُحيَّد أي = + - @ في البداية ضد حقن صيغ جداول البيانات.
  • secondaryLocations في Ashby (المشروع الأصلي #1073). يطوي مصدر Ashby الآن تسمية منطقة كل موقع ثانوي إضافةً إلى addressLocality / addressCountry البريديَّين داخل سلسلة الموقع (مع إزالة التكرار)، بحيث يظهر دور مؤهَّل للعمل في الاتحاد الأوروبي تقرأ تسميته الأساسية مثلًا “Canada” أمام location_filter.
  • التحقق من شكل تقرير التقييم (المشروع الأصلي #819). تُعلِّم المزوّدات داخل العملية في /api/evaluate (Anthropic / OpenAI / Qwen / OpenRouter / GitHub Models) الآن أي تقرير A–G / SCORE_SUMMARY مُشوَّه على هيئة مصفوفة warnings غير قاتلة؛ ومسار تقييم Gemini يرث هذه الحماية مسبقًا من gemini-eval.mjs الخاص بالمشروع الأصلي.
  • docs: أُضيف Antigravity CLI إلى قوائم المساعدين المدعومين في جميع ملفات README الاثني عشر (يُقابل مزوّد Gemini).

موروث مجانًا من git pull الخاص بالمشروع الأصلي (تستدعي web-ui هذه عبر shell out): الخط الاحتياطي CJK لملفات PDF اليابانية (#1053)، وخطوط PDF الآمنة لأنظمة ATS (#1074)، وحماية CJK لـ LaTeX (#1054)، وإصلاحات tracker/merge/followup/dashboard، وأوضاع modes/zh الصينية (تَسرُد web-ui الأوضاع ديناميكيًا).


[1.74.3] — 2026-06-18

docs(parent-source): توجيه مستودع career-ops الأصلي إلى تفرع Fighter90/career-ops. تشير واجهة web-ui الآن إلى تفرع المشرف بوصفه المشروع الأصلي في كل موضع يُستخدم فيه كمصدر فعلي: القيمة الافتراضية CAREER_OPS_REPO في مُثبِّت bin/setup.sh، وكل رابط git clone / «مبني فوقه» / الإعداد في جميع ملفات README الاثني عشر، ووثائق الوكلاء (CLAUDE.md وAGENTS.md وGEMINI.md و.github/copilot-instructions.md وdocs/). يبقى إسناد التأليف إلى santifer (وإخلاء المسؤولية عن الواجهة غير الرسمية) دون تغيير — انتقلت فقط روابط المصدر/الاستنساخ. يتحقق tests/sh-files.test.mjs الآن من أن المُثبِّت يستنسخ التفرع.


[1.74.2] — 2026-06-17

fix(health): إظهار GITHUB_MODELS_API_KEY كفحص اختياري في #/health وفي /api/status/providers. كان مزوّد GitHub Models في الإصدار v1.74.0 قابلاً للتهيئة في #/config لكنه لم يكن له صف في صفحة Health وكان مفقوداً من سطح المزوّدين keysConfigured. تمت إضافة الفحص الاختياري (بنفس صياغة “set / unset (manual mode)” المستخدمة مع المزوّدين الخمسة الآخرين للتقييم المباشر) وgithub (+ GITHUB_MODELS_MODEL الخاص به) إلى /api/status/providers، بحيث أصبح توجيه المزوّد النشط وصفحة Health يعكسان الآن جميع المزوّدين الستة. تم توسيع اختبار صف health في tests/api.test.mjs ليشمل جميع المزوّدين الستة.


[1.74.1] — 2026-06-17

docs + test: قسم “تثبيت مساعد ذكاء اصطناعي” في README؛ تغطية كاملة للفروع لموصّل Gemini. تمت إضافة جدول التثبيت/تسجيل الدخول إلى README — روابط التثبيت لـ Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI + تعيين مزوّد #/config لكل منها + «سجّل الدخول قبل المتابعة» (يعكس دليل البدء السريع في career-ops.org/docs؛ يوضّح أن واجهة الويب هي البديل المستقل الذي لا يحتاج إلى CLI). يغطّي tests/gemini-connector.test.mjs الجديد (8 حالات) كل فرع من فروع runGemini — بلا مفتاح، نجاح، خطأ في API، إكمال فارغ/محظور، جسم استجابة مشوّه، انتهاء المهلة، خطأ في الشبكة، hasGeminiKey — ليصل server/lib/gemini.mjs إلى 100% من التعليمات البرمجية. التغطية الإجمالية: 96% أسطر / 88% فروع / 96% دوال. مجموعة الاختبارات 1126 → 1134.


[1.74.0] — 2026-06-17

feat(llm): GitHub Models (Copilot) كمزوّد سادس + التوافق القانوني لستة مساعدين. يُدرج career-ops.org/docs ستة مساعدين لترميز الذكاء الاصطناعي — Claude Code، Gemini CLI، Codex، Qwen Code، OpenCode، GitHub Copilot CLI. تدعم web-ui الآن جميع الستة: خمسة منهم يرتبطون بمزوّدين نشطين موجودين (Anthropic / Gemini / OpenAI / Qwen / OpenRouter)، ويحصل GitHub Copilot CLI على موصّل مخصص لـ GitHub Models — runGitHubModels (OpenAI-compatible؛ PAT من GitHub بنطاق models)، قابل للتهيئة في #/config (GITHUB_MODELS_API_KEY + GITHUB_MODELS_MODEL) وقابل للتحديد عبر LLM_PROVIDER=github؛ السادس في ترتيب auto. تُدرج حزم المساعدة وملفات README الآن الستة القانونيين (تمت إعادة تسمية Qwen CLI إلى Qwen Code؛ تمت إضافة Gemini CLI + GitHub Copilot CLI)، ويُضيف README جدولاً كاملاً لمرجع الأوضاع وروابط محوّلات البوابات إلى career-ops.org/docs حتى يمكن تتبّع كل ميزة إلى المشروع الأصل. يُوسّع tests/llm-provider-context.test.mjs مصفوفة حدود الجلب لتشمل جميع المزوّدين الستة (cv.md + profile.yml مضمّنة + العناصر المُعادة)؛ تمت إضافة مفاتيح GITHUB_MODELS_* الجديدة إلى جميع قواميس 12 لغة إقليمية. الحزمة 1125 → 1126.


[1.73.0] — 2026-06-17

feat(llm): موصّل Gemini العام + سياق السيرة الذاتية/الملف الشخصي الموثَّق عبر جميع مزودي الخدمة. تمت إضافة server/lib/gemini.mjs (runGemini) — عميل Gemini generateContent بدون أي تبعيات خارجية يعيد نفس البنية {markdown, usage, error} التي يعيدها العملاء المتوافقون مع Anthropic / OpenAI. الإصلاح: كانت /api/mode/:slug و/api/deep تُمرّران المطالبات عبر gemini-eval.mjs المخصص للتقييم فقط، مما جعل Gemini Run live يعيد تقييماً بدلاً من القطعة المطلوبة (رسالة تقديم، تواصل، موجز). تستدعيان الآن runGemini مع bundleProjectContext، فيُدمَج cv.md + config/profile.yml مضمَّنَين لـ Gemini تماماً كما مع كل مزود آخر — فتصبح الرسائل والموجزات مفصّلة ومخصَّصة. يحاكي tests/llm-provider-context.test.mjs الجديد حدود HTTP لكل مزود ويتحقق من أن المزودين الخمسة جميعهم (Anthropic / Gemini / OpenAI / Qwen / OpenRouter) يُدمجون cv.md + profile.yml مضمَّنَين ويعيدون القطعة (مصفوفة mode + deep + evaluate، 9 حالات). تحتفظ /api/evaluate بـ gemini-eval.mjs المضبوط للوظائف. Suite 1116 → 1125.


[1.72.0] — 2026-06-17

feat(modes): يُعيد Run live الآن القطعة النهائية مباشرةً (عقد إخراج من تنفيذ واحد). كُتبت قوالب modes/<slug>.md الأصلية لجلسات Claude Code التفاعلية — إذ يتوقف بعضها (cover وcontacto وغيرها) لطرح أسئلة توضيحية قبل إنتاج النتيجة، مما كان يجعل Run live في واجهة الويب يُصدر استبياناً بدلاً من القطعة المطلوبة. يُغلّف buildModePrompt الآن كل وضع في عقد إخراج غير تفاعلي: يُجري التحليل (تفكيك وصف الوظيفة، وملاحظات الشركة، وكلمات ATS المفتاحية، والفجوات بين الملف الشخصي↔والوظيفة، وخيارات النبرة/الزاوية) بصمت، ويختار قيمًا افتراضية معقولة من cv.md / config/profile.yml لكل ما كان القالب يسأل عنه عادةً، ولا يُخرج سوى القطعة النهائية — مختتمًا بتذكير خاص بكل وضع «output ONLY {the cover letter / outreach message / …}». وهكذا، يُعيد النقر على Run live في #/cover الآن خطاب التغطية نفسه؛ ويسري الإصلاح ذاته على جميع الأوضاع العامة (cover وcontacto وinterview-prep وproject وtraining وfollowup وpatterns) في كل 12 لغةً محليةً (تُكتب القطعة بلغة الواجهة عبر توجيه اللغة المحلية). Suite 1103 → 1116.


[1.71.2] — 2026-06-17

docs(i18n): ينشر مراجعة اتساق الوثائق. يسرد قسم “Translations of this guide” في كل ملف README الآن جميع اللغات الـ 11 المرافقة (كانت بعضها تحذف English/Français أو تحتوي على رابط ذاتي)، مع استعادة السطر الفارغ قبل فاصل القسم. تمت إعادة تسمية موجّه انحدار ضمان الجودة الكامل إلى الإصدار الحالي، ومزامنة الوثائق (CLAUDE.md، CONVENTIONS، LOCALIZATION، PROJECT-CONTEXT) مع الإصدار الحالي وعدد الاختبارات (1103). لا تغييرات في الكود أو السلوك — وثائق فقط، لذا تظل ترجمات المساعدة/واجهة المستخدم وجميع الميزات من 1.70.0–1.71.1 دون تغيير.


[1.71.1] — 2026-06-17

fix(i18n): دليل المساعدة داخل التطبيق مترجم الآن بالكامل بجميع اللغات الـ 12. تمت إضافة docs/help/{pl,uk,ar}.md (يحتوي كل منها على البنية المُتحقق منها 19 H2 / 75 H3) حتى يقدّم #/help حزمةً أصيلةً باللغات البولندية والأوكرانية والعربية بدلاً من الرجوع إلى الإنجليزية — يعيد GET /api/help/{pl,uk,ar} الآن محتوى اللغة الخاصة به. تم ربطه بجميع بوابات المساعدة (help-ui، help.test، help-ru-config-section، canonical-docs-coverage). كما اكتملت جميع قوائم الترجمة بـ 12 لغة: قسم «Translations of this guide» في ملفات README (9 ملفات README)، وترويسات «Translations:» في ملفات CHANGELOG المترجمة (8 ملفات)، وتم تحديث أعداد التوثيق القديمة. Suite 1100 → 1103.


[1.71.0] — 2026-06-16

feat(cover): إنشاء ملف PDF لخطاب التقديم مباشرةً من #/cover. يُنتج وضع cover (المُضاف في v1.70.0) نص الخطاب؛ وتُقدِّم النتيجة الآن زرَّ Generate PDF الذي يُصيِّر النص عبر مسار markdown→PDF المُضمَّن المشترك (POST /api/stream/pdf/inline → generate-pdf.mjs)، وهو المسار ذاته الذي يستخدمه interview-prep. يمكنك الآن إعداد الخطاب وإرسال ملف PDF دون مغادرة SPA.

test/docs: تعزيز مراجعة v1.70.0. تمت إضافة تغطية معزولة عن CI لوضع cover (قائمة السماح + تجميع الطلب)، ومُبدِّل <select> الأعلام + RTL العربية (dirFor/<html dir>)، وtop.langLabel في كل إعداد محلي، وتوصيل PDF خطاب التقديم، وتوجيه الإعداد المحلي في prompts.mjs + السقالات البرمجية لـ fr/pl/uk/ar. تم تحديث المراجع القديمة «جميع الـ 8» إلى 12 إعداداً محلياً في docs/sdd/CONVENTIONS.md وفي طلب انحدار QA للمشروع بأكمله.


[1.70.0] — 2026-06-16

feat(i18n): ثلاث لغات واجهة مستخدم جديدة — البولندية (pl) والأوكرانية (uk) والعربية (ar، مع دعم كامل لـ RTL) — لترتفع بالـ SPA إلى 12 منطقة محلية، لتتطابق مع جميع اللغات الواردة في README مشروع career-ops الأصلي. تأتي كل منطقة محلية جديدة مزودة بقاموس كامل مؤلف من 697 مفتاحاً (public/js/lib/locales/i18n-dict.{pl,uk,ar}.js)، خاضعٍ للتحقق عبر مجموعات الاختبار الحالية للتكافؤ والتغطية وعدم تسرب الحروف اللاتينية وعدم وجود بيانات شخصية. يضيف الدعم العربي توجيهاً حقيقياً من اليمين إلى اليسار: يضبط i18n.js السمة <html dir="rtl"> للمناطق المحلية RTL، فيما يعكس كتلة [dir="rtl"] المحددة النطاق في app.css واجهة التطبيق (الشريط الجانبي، ودرج الإشعارات، والجداول وعروض الاقتباس في markdown، والمسافات المضمّنة) — بينما تبقى المناطق المحلية LTR دون تغيير بايت واحد. يُعرِّف المفتاح الجديد top.langLabel (×12) محدد اللغة لقارئات الشاشة.

feat(ui): يحل محدد اللغة <select> بأيقونات الأعلام محل صف الأزرار الذي كان يلتف على عدة أسطر. مع 12 منطقة محلية، كان صف .lang-btn القديم يمتد على ثلاثة أسطر في الشريط الجانبي؛ أما <select> الأصلي (كل خيار مسبوق برمز تعبيري لعلم) فيتوسع بشكل أنيق، ويدعم لوحة المفاتيح وقارئات الشاشة بدون إعداد إضافي، كما يظل آمناً مع CSP (معالج التغيير عبر addEventListener، دون JS مضمّن). تتراجع الأعلام إلى أحرف المنطقة حين تفتقر المنصة إلى رموز الأعلام، فتبقى تسمية اللغة دائماً المعرِّف الأساسي.

feat(cover): نقل وضع خطاب التقديم من المشروع الأصلي (career-ops v1.10.0 + تحية v1.11.0) إلى الـ SPA. صفحة #/cover جديدة ضمن مجموعة التنقل «الطلبات»، مبنية على منفذ الأوضاع العام: وصف الوظيفة + الشركة/الدور + تحية اختيارية ← خطاب مخصص مُولَّد من cv.md / modes/_profile.md. أُضيف cover إلى MODE_ALLOWLIST في الخادم وكتلة i18n باسم cover.* (×12 منطقة محلية).

chore(compat): تتبع مشروع career-ops الأصلي v1.11.0. تم التحقق من سلامة عقد القراءة/الكتابة — يبقى data/applications.md مصدر الحقيقة بصيغة markdown (فهرس تتبع SQLite في v1.11.0 هو ذاكرة تخزين مؤقت مشتقة)، وتظل أعمدة أداة التتبع معيَّنة بحسب الترويسة. يُبلّغ parentVersion الآن عن الإصدار 1.11.0.

fix(i18n): سد ثغرة كامنة تمثّلت في غياب اللغة الفرنسية (المضافة في v1.61.0) من LOCALE_NAMES وSCAFFOLD_STRINGS في server/lib/prompts.mjs — إذ كانت استدعاءات LLM بالفرنسية تعود صامتةً إلى إخراج باللغة الإنجليزية وسقالات إنجليزية. fr/pl/uk/ar جميعها متصلة الآن بمسار المنطقة المحلية للمطالبات.

متابعات معروفة: يعود دليل المساعدة داخل التطبيق (docs/help/) إلى اللغة الإنجليزية للمناطق pl/uk/ar (واجهة التطبيق نفسها مترجمة بالكامل)؛ كما أن إعداد المقابلة التفاعلي وميزة اكتشاف نظام ATS العكسي ومزودي الفحص الأحدث في المشروع الأصلي لم تُتَح بعدُ في الـ SPA.

[1.69.2] — 2026-06-12

fix(test): close a test-isolation leak that let npm test overwrite the user’s real config/profile.yml and data/scan-history.tsv. tests/critical-fixes.test.mjs statically imported prompts.mjs at the top of the file; prompts.mjs transitively imports paths.mjs, which resolves PROJECT_ROOT eagerly at module load (PATHS resolves once per process). Because that import ran before the before() hook set CAREER_OPS_ROOT to a temp dir, PATHS pinned the real parent — so the F-008 PUT /api/profile wrote the “Acceptance Test” fixture into the user’s real profile on every run (and similar writes escaped the temp root). Fix: load prompts.mjs via dynamic import() inside before(), after the env is set (the server was already loaded that way). New tests/test-root-isolation.test.mjs (2 cases) guards every isolation-needing test against statically importing a paths.mjs carrier (server/index.mjs, prompts.mjs, store.mjs, en-scanner.mjs, ru-scanner.mjs, paths.mjs). No production-code change. Suite 1084 → 1086.


[1.69.1] — 2026-06-12

fix(scan): raise the #/scan result display cap 500 → 2000 per region so large regional sweeps are no longer silently truncated. A real RU scan produced 1352 matching jobs, but only the first 500 were stored in data/last-scan.json and rendered in the results table — 852 relevant jobs were hidden (the 2000 scanned → ~600 shown symptom: 139 EN + 500 RU). Both server/lib/en-scanner.mjs and server/lib/ru-scanner.mjs now cap the stored filtered set at a shared, env-overridable constant MAX_STORED_RESULTS (default 2000, override via SCAN_MAX_RESULTS). This is display-only — appending to pipeline.md and scan-history.tsv already used the uncapped fresh (new-since-last-scan) set and was never truncated. New tests/scan-result-cap.test.mjs (3 cases) locks the default, the env override, and that neither scanner hard-codes slice(0, 500).

fix(health/ui): #/health check cards no longer overflow. A long check name/value (e.g. PROFILE CUSTOMIZED · still on template …, PLAYWRIGHT (PARENT NODE_MODULES)) collided with the right-hand Fix → button + status badge and spilled out of the card, because the generic .flex-between flex children default to min-width: auto and never shrink. The row is now tagged .health-check-row with scoped CSS: the left text shrinks + wraps (min-width: 0), the action group keeps its size (flex: 0 0 auto) and wraps below on narrow cards. New tests/health-card-overflow.test.mjs (2 cases). Suite 1079 → 1084.


[1.69.0] — 2026-06-12

feat(scan): P-14 plug-in scanner auto-discovery — drop a .mjs in server/lib/sources/ to register a new source. Pre-v1.69 the source list in server/lib/sources/registry.mjs was a static hand-maintained array — adding a new adapter required editing both <id>.mjs AND registry.mjs. Closes the partial half of the roadmap item P-14 (docs/ROADMAP.md). Now every *.mjs in server/lib/sources/ is auto-loaded at module boot; each adapter contributes its identity via a self-describing export const meta = { value, label, region, configKey? } block. The 12 shipped adapters (ashby / greenhouse / lever / rss / smartrecruiters / workable / workday + geekjob / getmatch / habr / hh / trudvsem) each grew a meta export; registry.mjs now uses readdirSync + dynamic import() resolved at module-eval via top-level await (Node 18+ ESM standard). The public API (SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) is unchanged — every existing import keeps working. Validation rejects malformed meta (missing value/label/region, RU without configKey, region outside 'en'|'ru') and logs a single console.warn per offending file so half-migrated branches stay diagnostic-friendly. The bundled registry.mjs is excluded from self-import. New tests/sources-registry-discovery.test.mjs adds 14 cases covering shipped-adapter coverage, drop-in adapter discovery, helper-module skip, malformed-meta rejection, self-import exclusion, missing-directory tolerance, and deterministic ordering. Suite 1065 → 1079.


[1.68.2] — 2026-06-07

fix(bin): npx / npm link CLI verbs were broken — resolve the bin path through symlinks. npm and npx expose career-ops-ui as a symlink under node_modules/.bin/, where the old dirname "${BASH_SOURCE[0]}" resolved to .bin instead of the package root — so npx career-ops-ui init ran node node_modules/scripts/init.mjs and crashed with MODULE_NOT_FOUND (local runs after npm install were unaffected, which hid the bug). Both bin/career-ops-ui.sh and bin/start.sh now canonicalize SCRIPT_DIR through the symlink chain (readlink loop + cd -P), so every verb works from the repo, via npm link, and via npx. Adds a regression lock in tests/sh-files.test.mjs that runs a verb through a .bin-style symlink. Suite 1065/1065.


[1.68.1] — 2026-05-29

fix(scan): per-source fetch timeout 10s → 60s. v1.67.1’s 10s fail-fast also cut off slow-but-alive Ashby boards that just needed more time. Raise the default to one minute so those return. Trade-off: a genuinely dead/hung source now holds a concurrency slot for the full 60s (slower worst-case scan), and the chronic hangers (Perplexity, Supabase, Resend, …) likely still time out — a per-source / lower-Ashby-concurrency fix would address those properly. Override with SCAN_FETCH_TIMEOUT_MS. Suite 1063/1063.


[1.68.0] — 2026-05-29

feat(scan): reworked the result-filter panel — labelled fields, an Apply button, an On-site option, and a working salary filter. Every filter on #/scan is now a labelled field (label above the control, not a placeholder): Search · Work type · Salary from · Salary to · Source · Scope. An explicit Apply button (plus Reset, and Enter in any field) re-runs the filter; an on-page hint explains how it works. The salary range now actually filters — once you set a from/to value, jobs whose listed pay falls outside the range and jobs with no listed salary at all are dropped (overlapping-range match; currency ignored). The Work type filter gains an On-site option alongside Remote / Hybrid / Relocation. New i18n keys ×9; salaryInRange made strict; suite 1063/1063.


[1.67.1] — 2026-05-29

fix(scan): per-source fetch timeout 30s → 10s (fail-fast). v1.67.0’s 30s raise recovered only ~half the slow Ashby boards; the rest (Perplexity, Supabase, Resend, DeepL, Ramp, …) hang regardless of the deadline, so a longer timeout just stalled every scan waiting on dead slots. 10s fails fast on the chronic hangers and keeps scans responsive. Override with SCAN_FETCH_TIMEOUT_MS. Suite 1060/1060.


[1.67.0] — 2026-05-29

feat(scan): salary range filter (from / to) on #/scan, plus a longer per-source fetch timeout. The results table gains two numeric inputs — salary from / to — beside the text and remote filters. Each row’s free-text salary (от 100 000 до 200 000 ₽, 120000-150000 USD, $120K–$150K, …) is parsed to a numeric range and matched with overlapping-range semantics; rows with no published salary are kept, so the filter narrows the list instead of gutting it (comparison is currency-agnostic — no FX conversion). Also raises the per-source scan fetch timeout 15s → 30s (override: SCAN_FETCH_TIMEOUT_MS) — Ashby’s includeCompensation payloads routinely took >15s under 8-way concurrency, so ~30 Ashby boards were timing out every scan. New window.Skills.parseSalaryRange/salaryInRange + i18n ×9; 13 new tests; suite 1060/1060.


[1.66.0] — 2026-05-28

feat(scan): RU sources now walk ALL result pages, not just the first. hh.ru, Habr Career and Trudvsem each only paged the first ~50 hits per query; they now follow pagination to the end — &page=N for hh.ru/Habr, offset/meta.total for Trudvsem — deduping across pages and stopping when a page adds nothing new (or at a 50-page safety cap). A query like “Backend разработчик” now returns the full result set instead of one page (e.g. hh.ru PHP 17 → 55+ across 3 pages; Trudvsem returns all 72). Per-page fetches keep the existing timeout + AbortSignal. 4 new tests; suite 1045/1045.


[1.65.0] — 2026-05-28

feat(scan): hh.ru is now scraped from its public website instead of the JSON API — works from any IP, no proxy. api.hh.ru started returning a bare 403 forbidden to every programmatic client regardless of IP (US, RU datacenter, RU residential, RU mobile) or User-Agent — an edge anti-bot block, not a documented API error. The website (hh.ru/search/vacancy) still serves full server-rendered results to any browser-like client, so the adapter now parses that HTML (like Habr Career). Removes the HH_PROXY env added in 1.64.0 and the undici dependency — no proxy, key, or User-Agent setup needed. Tests rewritten for the HTML parser; suite 1041/1041.


[1.64.0] — 2026-05-27

feat(scan): route the hh.ru request through a Russian proxy via HH_PROXY. hh.ru geo-blocks its API by IP, not User-Agent — so HH_USER_AGENT alone never lifted a 403 from a non-RU exit node. Set HH_PROXY to a Russian HTTP/HTTPS proxy URL (e.g. http://user:pass@ru-host:port) and only the hh.ru request is routed through it; every other source keeps its direct connection. Built on undici’s ProxyAgent (new runtime dep); the dispatcher is omitted entirely when HH_PROXY is unset, and a changed value is picked up on restart. 3 new tests; suite 1041/1041.


[1.63.2] — 2026-05-27

feat(scan): live % progress + per-source detail in the #/scan console. The progress bar is now determinate — scanners emit progress events (EN: per company; RU: per query) forwarded over SSE, and the bar fills with a live “Scanning… NN%” label (animated indeterminate stripe only until the first event). Each source’s first failure (timeout / 403 / network) is now logged to the console in detail (e.g. ⚠ hh timed out: …), then repeats are suppressed. 1 new test; suite 1040/1040.


[1.63.1] — 2026-05-27

style(scan): make the #/scan progress bar more prominent. Wrapped the in-flight indicator with a visible “Scanning…” caption and bumped the bar to 8px (was a thin 4px) so it’s clearly noticeable while a scan runs. No behavior change.


[1.63.0] — 2026-05-27

feat(scan): per-request fetch timeout + #/scan progress bar. Scanner source requests had no deadline, so a stalled upstream (e.g. api.hh.ru from a blocked IP) could hang the whole scan. A new server/lib/fetch-timeout.mjs wraps the scanners’ fetchImpl (makeTimeoutFetch, default 15s, override via SCAN_FETCH_TIMEOUT_MS) so every source request has a hard deadline — a timed-out source is recorded as a non-fatal error and the scan continues (Habr keeps working even when hh.ru is unreachable). The #/scan page also shows an indeterminate progress bar while a scan is in flight (localized scan.progress across all 9 locales). 7 new tests; suite 1039/1039.


[1.62.3] — 2026-05-27

docs: clarify install (career-ops-ui runs inside career-ops/web-ui/) + init troubleshooting, across all 9 locales. Rewrote the install section into Option 1 (one curl) / Option 2 (clone the UI inside an existing career-ops project as web-ui) + CLI verbs + provider setup + a Troubleshooting init block — addressing the common confusion of cloning career-ops-ui standalone and running init without the parent project. Also added the nested-layout note to the /help §1 Setup section and summarized the whole v1.62.* line in the README highlight. Docs-only; no code change.


[1.62.2] — 2026-05-27

fix(help): #/help filter is now full-text (finds H3 subsections like RSS). The help-page search/TOC filter previously matched only H2 section titles, so the v1.62.x RSS docs (an H3 under §5 Portals & sources) weren’t findable. Each section’s body text is now indexed into the filter, so searching e.g. “RSS” surfaces §5. Pure client-side; no API change.


[1.62.1] — 2026-05-27

feat(scan): RSS in the source filter + RSS location fix. The #/scan source-filter dropdown now lists RSS (added to server/lib/sources/registry.mjs + the SPA fallback list), so RSS-board results (LaraJobs, WeWorkRemotely, …) are filterable like any ATS source. The RSS adapter no longer maps the feed <category> tag onto location — non-location tags there made location_filter wrongly drop remote roles; location is now empty so feeds pass location filtering. Scan-button tooltips/labels and the source-list i18n string (dash.quick.scanSub) updated across all 9 locales to include Workable / SmartRecruiters / Workday / RSS. i18n snapshot + scan-sources endpoint test (6 → 7 EN sources) updated.


[1.62.0] — 2026-05-27

feat(scan): generic RSS adapter for non-ATS job boards. A new rss portal adapter (server/lib/portals/adapters/rss.mjs + server/lib/sources/rss.mjs) lets the scanner pull jobs from any RSS feed — LaraJobs, WeWorkRemotely, RemoteOK, golangprojects and other boards outside Greenhouse/Ashby/Lever. Zero new dependencies: feed parsing is regex-based with CDATA + HTML-entity support (titles/companies are tag-stripped, astral code points decoded safely). Activated per-company via provider: rss / rss: / feed_url: in portals.yml, so it never intercepts ATS-matched companies. ALL_ADAPTERS grows 6 → 7. 29 new tests; documented across all 9 README locales.


[1.61.1] — 2026-05-22

fix(i18n): localize the theme-toggle title + aria-label across all 9 locales (MINOR-001). The dark/light theme button (#theme-toggle) hardcoded title="Toggle theme" and aria-label="Toggle theme" in index.html — the tooltip and screen-reader text never translated, on any locale. A new top.themeToggle key + a data-i18n-title handler in applyI18n() (mirroring the v1.58.15 search-aria-label fix) localize both attributes on boot and on every language switch. Locked by tests/playwright-theme-toggle-i18n.mjs (9 locales + runtime-switch) and two static guards in tests/qa-report-fixes.test.mjs. The lone LOW finding from the v1.61.0 French sign-off. (MINOR-001)


[1.61.0] — 2026-05-22

feat(i18n): add French as the 9th UI language. New per-locale dictionary public/js/lib/locales/i18n-dict.fr.js (window.__I18N_DICT_FR), at full 668-key parity with English; new help bundle docs/help/fr.md (19 H2 / 73 H3, exact structural parity with en). fr is registered in the language switcher and browser auto-detect (i18n.js), the assembler (i18n-dict.js), index.html (a <script> tag before the assembler), the test snapshot, and every test locale list. The initial translation table came from PR #9 (community contribution). No logic change: t() and every view are unchanged. 1001 / 1001 unit tests; the Playwright locale-sweep grows to 9 subtests. (FR-LOCALE)


[1.60.0] — 2026-05-22

refactor(i18n): split the 8-column megafile into per-locale files (I18N-SPLIT). The translation dictionary lived in one 849-line public/js/lib/i18n-dict.js (key → { en, es, … } across 8 locales). It is now one file per locale — public/js/lib/locales/i18n-dict.{en,es,pt-BR,ko,ja,ru,zh-CN,zh-TW}.js (each assigns window.__I18N_DICT_<LANG> = { key: string }) plus a shared i18n-dict.aliases.js — so a translator edits a single language in isolation (the i18next / OpenWA layout). i18n-dict.js is now a small assembler that merges the per-locale tables back into the exact same window.__I18N_DICT shape, so t(), every view, and every call-site are byte-for-byte unchanged. Everything loads synchronously via <script src> — no build step, no runtime fetch (both hard rules preserved). A captured snapshot proves the migration is lossless (assembled dict ≡ pre-split dict, 678 keys). Tooling + tests made split-aware via a shared tests/helpers/i18n-vm.mjs: tools/i18n-audit.mjs, the CI inline coverage check (which had silently become a no-op against the post-v1.23 split — it now actually loads the dictionary), and ~25 dictionary tests. New tests/i18n-locale-files.test.mjs (snapshot equality · per-locale key parity · alias integrity · index.html load order) and tests/playwright-locale-sweep.mjs (every page × 8 locales renders + localizes in real Chromium). 994 → 1000 unit · 62 → 70 Playwright. No user-facing behaviour change. (I18N-SPLIT)


[1.59.13] — 2026-05-21

fix(i18n): collapse TRUE duplicate keys via @alias + final personal-data purge (pre-fr, follow-up to v1.59.12). Two parts. (1) Personal data: swept the WHOLE repo, not just the dict — the maintainer’s real name was used as incidental test-fixture data (tests/health-doctor-unify.test.mjs, tests/llm-output.test.mjs) and in archived QA reports; all replaced with neutral examples (Jane Doe). LICENSE copyright + package.json author switched from the real legal name to the public Fighter90 handle. Repo now greps clean for the real name/email everywhere. (2) Duplicate keys (I18N-CL3, properly): added an @alias mechanism to i18n.t() — a key { '@alias': 'x.y' } resolves to a canonical key. Collapsed the 10 keys that are byte-identical across all 8 locales (nav.help/dash.quick.helpCta → help.title; nav.cv → cv.title; nav.health/dash.quick.healthCta → health.title; nav.reports/dash.reports/dash.quick.reportsCta → rep.title; nav.apply → apply.title; nav.interviewPrep → interviewPrep.title). The contributor’s own nav.config/config.title example is deliberately NOT aliased — they diverge in Spanish (short sidebar Configuración vs full title Configuración de la aplicación); aliasing would change a rendered label. Coverage test, CI inline check, and tools/i18n-audit.mjs are all alias-aware (skip alias keys for parity, enforce every target exists, resolve before dup-counting). New tests/i18n-alias.test.mjs (3 cases) locks integrity + behaviour. 991 → 994 unit. Reduces the French translator’s surface — true dupes are now translated once. (I18N-CL3, personal-data-sweep)


[1.59.12] — 2026-05-21

fix(i18n): i18n-dict.js cleanup — pre-fr ship (I18N-CL1, I18N-CL2, I18N-CL4). External French-locale contributor audit. I18N-CL1 (privacy): training.coursePh carried a vendor-specific cert name across all 8 locales — replaced with a neutral generic placeholder (Cloud architecture certification + native equivalents). I18N-CL2 (hygiene): the followup.lastPh dict fallback was a frozen 2026-04-21 literal — replaced with a localized format hint (YYYY-MM-DD / AAAA-MM-DD / ГГГГ-ММ-ДД); the live dynamic today − 14d placeholder (U-3, v1.58.23) in mode-page.js was already working and is now lock-tested. I18N-CL4 (audit): new tools/i18n-audit.mjs + npm run audit:i18n, wired into CI — hard-fails on personal data, locale-parity gaps, empty values, and bare-calendar-date placeholders. I18N-CL3 (decision): the ~50 duplicate-value key groups (e.g. nav.scan / scan.btnRun / scan.col.company) are intentional — distinct UI roles that non-English locales translate differently; deduping them would remove i18n flexibility, so they are reported as informational warnings, not failures (documented in the dict header). 3 new tests (988 → 991 unit). Unblocks the upcoming French locale PR. (I18N-CL1, I18N-CL2, I18N-CL4)


[1.59.11] — 2026-05-21

fix(test): v1.59.11 — e2e-comprehensive 12-case CI failure root-caused and closed. Twelve cases (Pipeline · Activity · Health · 7 Mode pages · 404 · Profile) had been failing on every CI run going back to v1.58.x because page.goto(baseUrl + '/#/X') is a no-op for hash-only URL changes in Playwright. Once the CV-save step set the hash to #/cv, every subsequent goto to a hash route silently kept the page on #/cv — Activity/Health/Mode/Profile selectors never matched, and the 4 ‘pass’ results in between were vacuous (their assertions found CV’s elements). Fix in tests/e2e-comprehensive.mjs: new goRoute(hash) helper that bounces through about:blank before each goto, forcing a real navigation and SPA re-bootstrap. All 17 page.goto(${baseUrl}/#/…) call sites replaced. Diagnostic instrumentation (E2E_DUMP_ON_FAIL=1 env var) added for future investigations. Result: 23 / 23 cases green locally · 988 / 988 unit · 20 / 20 smoke e2e. (e2e-harness-r1)


[1.59.10] — 2026-05-21

fix(api): NEW-F1-sub-r1 (v1.59.10) — un-encoded ../ traversal guard hoisted above all /api route registrations. v1.59.8 added a req.originalUrl.includes('..') middleware, but it was placed AFTER app.all('/api/*', JSON-404) AND AFTER all route handlers — by which time Express had already normalised the URL (collapsing .. segments). /api/jds/../../../etc/passwd was rewritten to /etc/passwd and fell through to the SPA static handler (200 OK on index.html). v1.59.10 hoists the guard to the TOP of server/index.mjs createApp() (above every register*Routes(app) call) so it inspects the verbatim request URL before any normalisation. Pattern: /^\/api(\/|$)/.test(req.originalUrl) && /\.\.\//.test(req.originalUrl). New tests/api-path-traversal.test.mjs — 6 cases driving the real http module (Node’s fetch normalises .. client-side, so we drop to http.request with a verbatim path). Unknown /api/* paths still return {error: 'unknown api'} from the existing app.all fallback. 982 → 988 unit. (NEW-F1-sub-r1)


[1.59.9] — 2026-05-21

fix(ux): UX-A5-r4 (v1.59.9) — Help TOC scroll-spy debug marker + behavioural lock-test. Sixth-cycle closure: previous five attempts (v1.58.45 / v1.58.52 / v1.59.0 / v1.59.3 / v1.59.8) all shipped with passing static tests but the bug stayed open because the tests asserted source-shape, not behaviour. v1.59.9 fixes the gap: (1) <body data-toc-spy="active"> debug marker — single selector any tester can use to answer “is the spy alive?” without needing to scroll; (2) synchronous initial paint at mount tail covers the router-pre-paints-view case; (3) double-rAF re-compute covers the route-handler-returns-before-router-appends case; (4) resize listener subscribed so viewport-flip mid-scroll re-paints; (5) cleanup removes BOTH listeners and the marker on hashchange. public/js/views/help.js algorithm: linear scan with else break; short-circuit (O(active-index) per scroll event). New tests/help-toc-spy-behavior.test.mjs runs the algorithm against 6 synthetic-geometry scenarios + 1 algorithm-parity check against help.js source — the test fails if the algorithm regresses, before any browser run. 973 → 982 unit. (UX-A5-r4)


[1.59.8] — 2026-05-21

fix(ux+api): v1.59.8 — UX-A5-r3 + NEW-F1-sub (HIGH + LOW bundled per FIX-PROMPT-FINAL-CONSOLIDATED). Doctrine-exception release: the 2026-05-20 FINAL REGRESSION-v1.59.7 report explicitly recommended bundling these two. UX-A5-r3 (HIGH) — after 4 ship attempts with IntersectionObserver all failed live verification, public/js/views/help.js replaces the IO entirely with a plain scroll listener. Root cause: with rootMargin -20%/-55% the trigger band ended at 45% of viewport, but scrollIntoView({block:‘center’}) lands the target at 50% — JUST below the band end. The new scroll listener probes absolute heading positions every rAF, computes the heading whose top is at-or-above 30% of viewport, applies .toc-current — no band, no race, no mount-order gotcha. NEW-F1-sub (LOW) — server/index.mjs adds a late middleware that inspects req.originalUrl (not normalised req.url) and bounces any /api-prefixed request whose raw URL contains .. as 404 JSON {error: 'invalid path'}. New UX-A5-r3 lock-test + NEW-F1-sub static + behavioural guards in tests/. 971 → 973 unit. (UX-A5-r3 · NEW-F1-sub)


[1.59.7] — 2026-05-20

fix(api): NEW-D3-cache (v1.59.7) — GET /api/cv sends Cache-Control: no-store. Matches the SPA-shell policy (W-001 / v1.54.7). cv.md is the user-edited primary artifact; a stale browser cache or intermediary proxy could surface yesterday’s text and trick the editor into saving over the live version. No ETag dance — the file is small and the GET is rare. New test in tests/api-404-json.test.mjs (the suite is the in-process server harness; named for its first test). 970 → 971 unit. (NEW-D3-cache)


[1.59.6] — 2026-05-20

feat(a11y): NEW-D2-motion (v1.59.6) — honour prefers-reduced-motion: reduce. New @media (prefers-reduced-motion: reduce) block in public/css/app.css neutralizes animation-duration + transition-duration to 0.01 ms and forces scroll-behavior: auto (so the Help TOC click-to-scroll lands instantly). WCAG 2.3.3 (AAA) — users with vestibular disorders, motion sensitivity, or simply OS-level reduced-motion preference now get an animation-free UI. Static guard in tests/qa-report-fixes.test.mjs. 969 → 970 unit. (NEW-D2-motion)


[1.59.5] — 2026-05-20

fix(api): NEW-F1 (v1.59.5) — unknown /api/* returns JSON 404 on every verb. Pre-fix app.get("/api/*", …) was GET-only; POST / PUT / DELETE to an unknown api path fell through to the SPA catch-all and returned an HTML 404, breaking SPA clients that do try { res.json() } catch {}. Changed to app.all("/api/*", …) in server/index.mjs. New test suite tests/api-404-json.test.mjs probes GET / POST / PUT / DELETE on /api/no-such-endpoint plus an unknown :name under a real handler — 5 new tests, all green. 964 → 969 unit. (NEW-F1)


[1.59.4] — 2026-05-20

fix(ui): NEW-OR1 (v1.59.4) — #/config API-keys summary chip is race-safe. User-reported transient Keys: 0 / 5 flash during Save flows. The previous refreshApiSummary() cleared <span> children before awaiting the network fetch; a concurrent providers-changed event could observe the empty state. New implementation in public/js/views/config.js:\n\n1. Build new <span> nodes before any DOM mutation.\n2. Atomic swap via apiSummary.replaceChildren(newActive, newCount) — chip never blanks mid-update.\n3. inFlight token counter drops stale resolves when a newer refresh starts.\n4. lastGoodSt cache preserves the last known state when fetch returns null (network blip, server reload), so the chip never collapses to 0 / 5 on a transient empty response.\n\nLock-test in tests/qa-report-fixes.test.mjs asserts all four invariants. 963 → 964 unit. (NEW-OR1)


[1.59.3] — 2026-05-20

fix(ux): UX-A5-r2 (v1.59.3) — Help TOC scroll-spy harden (third-pass fix). Two real issues persisted after v1.58.52: (1) rootMargin: "-30% 0% -60% 0%" left only a 10% visible band so fast scroll could skip the trigger zone entirely and no IntersectionObserver entry ever fired; (2) no initial-state computation, so a freshly-loaded #/help with zero scroll showed zero highlights even though section 1 was visibly active. public/js/views/help.js: widened rootMargin to -20% 0% -55% 0% (25% band), made root: null explicit, extracted applyCurrent(id) so the same code path serves both observer callbacks and the initial-state pass, and on mount picks the heading closest to 20% from top of viewport. 962 → 963 unit. (UX-A5-r2)


[1.59.2] — 2026-05-20

fix(ui): v1.59.2 — UX-A9 / UX-A3 chip count + overlap (post-v1.59.1 hotfix). Three real bugs in the v1.58.62 / v1.58.55 chips, user-reported on the staging surface:\n\n1. Always Keys: 0 / 5. /api/status/providers returns keysConfigured as an array of provider names; pre-fix typeof === number was always false. Now uses Array.isArray(st.keysConfigured) ? st.keysConfigured.length : 0.\n2. Lowercase provider name. Server returns anthropic (not claude — LLM_PROVIDER env value vs resolved provider name); the NAME map was keyed by claude: so it always fell through to the lowercase fallback. Re-keyed {anthropic, gemini, openai, qwen, openrouter} in both public/js/views/config.js and public/js/views/dashboard.js.\n3. Sticky chip overlapped other elements. .api-keys__summary had position: sticky + z-index: 5 and created a stacking context that floated above the tablist + page header on scroll. Dropped sticky — the chip is at the top of a short panel already. Lock-test updated to forbid the sticky position. (No version bump alone — counts on existing UX-A3 / UX-A9 lock-tests.) (post-cycle hotfix)


[1.59.1] — 2026-05-20

fix(test): v1.59.1 — NEW-D1 i18n-no-latin-leaks guard accepts UX-A11 polished ES copy. During final verification of the v1.59.0 ship I caught a regression: the v1.58.37 NEW-D1 static guard asserted pipe.title[es] must match /vacant|vaca/i (locking the old Pipeline de vacantes). UX-A11 (v1.58.64) refined that to Pipeline de candidaturas (candidate-side perspective). Fix: relax the regex to /vacant|vaca|candidatur/i so either contextualizing noun passes. No production-code change. 962 unit tests, 0 fail.


[1.59.0] — 2026-05-20

feat(ui): UX-A14 (v1.59.0) — Mobile (≤ 420 px) media-query pass. Closes the long-standing mobile audit. Five focused fixes inside a single @media (max-width: 420px) block in public/css/app.css:\n\n1. .card-row (4 dashboard metric cards) collapses to 1-up below 420 px (was a 900+ px horizontal scroll).\n2. .dash-hero-cta stacks vertically with full-width buttons (was side-by-side wrap-mid-label on iPhone SE).\n3. .page-header stacks H1+subtitle above the action buttons row (was right-side overflow on 360 px).\n4. .qa-grid minmax floor drops from 220 px to 160 px so tiles fit two-up on 360–390 px viewports.\n5. .api-keys__summary chips tighten horizontal padding for breathing room.\n\nRegression-lock test in tests/qa-report-fixes.test.mjs. 961 → 962 unit. (UX-A14)


[1.58.65] — 2026-05-20

test(ui): UX-A2 (v1.58.65) — lock-test on Modes structured field-form. The 5-field structured form for #/config → Modes was already shipped in v1.54.3 (canonical Target Roles / Adaptive Framing / Exit Narrative / Comp Targets / Location Policy + repeatable line-inputs for list-kind fields + × remove + add-row affordance + tagged {mode: sections|markdown} collect()). The UX-A2 audit assumed it was missing because all 5 sections initially appeared as <textarea> (an artifact of the field-form scaffolding pass before file load). This regression-lock test in tests/qa-report-fixes.test.mjs prevents a future PR from collapsing the field-form back to raw markdown. No code change. 960 → 961 unit. (UX-A2)


[1.58.64] — 2026-05-20

fix(i18n): UX-A11 (v1.58.64) — es / pt-BR copy polish: English loanwords replaced. eval.subtitle (es) now uses ajuste del CV / Puntaje / cabecera / informe; pt-BR uses aderência do CV / Pontuação / cabeçalho / relatório. pipe.title (es) refined to Pipeline de candidaturas (candidate-side perspective). pt-BR vagas retained. 959 → 960 unit. (UX-A11)


[1.58.63] — 2026-05-20

fix(ui): UX-A15 (v1.58.63) — Dashboard Pipeline tile gets visual primary weight. The Quick-actions grid previously gave every tile equal weight; the highest-frequency action (Pipeline) didn’t draw the eye. The qa() helper in public/js/views/dashboard.js now accepts a 7th primary flag; the Pipeline tile is the only one passing true so it gets a subtle accent border + larger icon + bolder label via .qa-tile--primary (public/css/app.css). Tile order, copy, click target, and accessibility name are unchanged. Lock-test in tests/qa-report-fixes.test.mjs. 958 → 959 unit. (UX-A15)


[1.58.62] — 2026-05-20

feat(ui): UX-A9 (v1.58.62) — #/config → API keys panel sticky summary chip. Before this release, with 5 provider key sections stacked vertically on the API-keys tab, the user had to scroll to know which provider the OR-fallback resolves to or how many keys were configured. public/js/views/config.js now renders a position: sticky .api-keys__summary at the top of the apiPanel showing Active: <provider> + Keys: <count>/5. Reuses /api/status/providers (no new API surface). Refreshes on providers-changed (after Save). Two new i18n keys (config.activeProvider, config.keysConfiguredPrefix) × 8 locales; one CSS rule (.api-keys__summary); static guard in tests/qa-report-fixes.test.mjs. 957 → 958 unit. (UX-A9)


[1.58.61] — 2026-05-20

docs(readme): UX-A8 (v1.58.61) — first-run cleanup section added across all 8 READMEs. Fresh clones of career-ops include two QA fixture URLs (example.com/qa-fixture-*) in data/pipeline.md so the test suite can run hermetically; the README never documented this so first-time users mistook them for real jobs. New ## First run — clean state section (mirrored in es / pt-BR / ko / ja / ru / zh-CN / zh-TW) instructs make clean-test-fixtures && npm start before the first scan. Lock-test in tests/qa-report-fixes.test.mjs verifies all 8 READMEs reference make clean-test-fixtures and qa-fixture-*. 956 → 957 unit. (UX-A8)


[1.58.60] — 2026-05-20

feat(ui): UX-A12 (v1.58.60) — Notifications drawer Clear all + per-entry dismiss. The v1.58.34 notifications journal capped at 50 entries but offered no manual purge. New UI.clearToastHistory() and UI.dismissToastHistory(ts) in public/js/api.js mutate toastHistory in place and notify subscribers with sentinel events ({cleared: true} / {dismissed: ts}); the drawer subscriber in public/js/app.js detects them, re-renders, and — critically — does NOT bump the unread counter on purges. Drawer head now carries a Clear all button (auto-hidden when the journal is empty); every .notif-item carries a × dismiss button. Three new i18n keys (notif.clearAll, notif.clearAllAria, notif.dismiss) × 8 locales; CSS for both controls; lock-test in tests/qa-report-fixes.test.mjs. 955 → 956 unit. (UX-A12)


[1.58.59] — 2026-05-20

feat(ui): UX-A13 (v1.58.59) — actionable “Fix →” CTA on failing #/health rows. Pre-fix the 21 health rows showed status only; a user with Profile customized: FAIL had to guess where to go. public/js/views/health.js now renders a small ghost-button Fix → next to the badge on failing/optional rows. Mapped targets: Profile customized → #/config?tab=profile, cv.md non-empty → #/cv, portals.yml present → #/config?tab=portals, data/applications.md → #/tracker. Any *_API_KEY or LLM_PROVIDER* row routes to #/config?tab=api-keys via regex fallback. Unmapped failures stay action-less (no dead-end CTAs). Two new i18n keys (health.fix, health.fixAria) × 8 locales. 954 → 955 unit. (UX-A13)


[1.58.58] — 2026-05-20

fix(ux): UX-A10 (v1.58.58) — guard #/cv against leaving with unsaved buffer. Pre-fix, navigating away from #/cv with unsaved edits silently dropped the buffer. public/js/views/cv.js now registers a beforeunload listener (browser-close confirm — generic dialog per modern browser policy) and a hashchange listener (SPA-internal nav prompts via localized window.confirm() and rewinds the hash if the user cancels). cvDirty lives in the save-button IIFE closure; cleanup self-detaches when the hash leaves #/cv (M-1 discipline). One new i18n key (cv.unsavedConfirm) × 8 locales. 953 → 954 unit. (UX-A10)


[1.58.57] — 2026-05-20

test(ui): UX-A7 (v1.58.57) — lock-test on the cost-line auto-refresh contract. v1.58.41 (UX-D-I) wired UI.providerCostHint to refresh when the user changes LLM_PROVIDER mid-session, but the contract had no static guard — any of the three pieces (config.js dispatch, api.js subscribe, advisor-view call site) could regress silently and the cost line would lie. New regression test in tests/qa-report-fixes.test.mjs locks all three: (1) config.js Save handler dispatches providers-changed, (2) UI.providerCostHint subscribes via document.addEventListener, (3) all 4 advisor views (#/deep, #/evaluate, #/auto, #/<mode>) call UI.providerCostHint(t). 952 → 953 unit. (UX-A7)


[1.58.56] — 2026-05-20

fix(a11y): UX-A4 (v1.58.56) — .lang-btn meets WCAG 2.5.8 minimum touch-target. Pre-fix the language buttons in the sidebar footer measured 23–25 px tall × 47–72 px wide (below the 24×24 WCAG 2.5.8 / WCAG 2.2 AA floor). public/css/app.css now declares min-height: 28px + min-width: 28px + padding: 6px 10px on .lang-btn — every locale label clears the floor with comfortable margin. Sidebar grows by 6 px total (negligible). Lock-test in tests/qa-report-fixes.test.mjs. 951 → 952 unit. (UX-A4)


[1.58.55] — 2026-05-20

feat(ui): UX-A3 (v1.58.55) — Dashboard active-provider chip. Above-the-fold chip in public/js/views/dashboard.js hero now surfaces whether the OR-model fell through to a live provider (e.g. ⚡ Live evals: Anthropic claude-sonnet-4-6) or whether the user is in 📋 Manual prompt mode (no API key set). Re-fetches on providers-changed (dispatched by #/config save) and on visibilitychange (cross-tab refocus). Reuses the existing /api/status/providers endpoint (no new API surface). Two i18n keys × 8 locales (dash.provider.live, dash.provider.manual); one CSS rule (.dash-chip--provider). Lifecycle cleanup on hashchange away from #/dashboard so listeners don’t stack across navigation (M-1 discipline). 950 → 951 unit. (UX-A3)


[1.58.54] — 2026-05-20

fix(ux): UX-A1 (v1.58.54) — defensive Deep-brief structure warning. The canonical Deep-research brief promised in career-ops.org/docs has six H2 sections (Company snapshot / Engineering culture / Recent news / Glassdoor / Interview process / Negotiation leverage). When the upstream prompt drifts and the saved brief is meta-narration instead of the final form, public/js/views/deep.js now detects the regression (≥3 of the six H2s missing) and prepends a non-blocking .brief-warning card explaining what the brief should look like and linking to the canonical reference. The root prompt-layer fix lives in the parent project (modes/deep.md, blocked from here); this UI guardrail surfaces the drift instead of silently rendering a degenerate brief. CSS rule added to public/css/app.css; three new i18n keys (deep.briefUnstructured.title / .body / deep.docsLink) cover all 8 locales. New static guard in tests/qa-report-fixes.test.mjs. 949 → 950 unit. (UX-A1)


[1.58.53] — 2026-05-20

fix(ux): UX-A6 (NEW-M4-r1) — every saved-research card flows through a single renderSavedCard() helper. v1.58.51 verification regression observed: one card rendered with no structural children (title+date as a single concatenated text node — software-engineer-generalyesterday) while another rendered with the proper <span> + <time> shape — depending on whether the card was page-load-rendered or runtime-inserted. Fix in public/js/views/deep.js: extracted renderSavedCard(f) that always emits .saved-card__title + .saved-card__date datetime=…. Whatever render path inserts a card (page-load renderArchive, post-Run live, or future code paths) routes through this single helper — the M-4 v1.58.11 flex-gap layout only works with the structural children present. 948 → 949 unit. (UX-A6)


[1.58.52] — 2026-05-20

fix(ux): UX-A5 (NEW-K1) — #/help TOC scroll-spy actually fires now (regression from v1.58.45). v1.58.51 verification regression caught: 18 H2 elements with ids, 18 TOC <a> links, but 0 links ever received .toc-current after scroll. Root cause: v1.58.45 wired the observer inside a setTimeout(0) that fired BEFORE the router appended the view to #content; document.querySelectorAll(".help-article h2[id]") matched nothing. Fix in public/js/views/help.js: new mountTocSpy() observes the synchronously-built headings refs we already hold (no document re-query); deferred via double requestAnimationFrame so it fires after the first paint that includes the mounted view. 947 → 948 unit. (UX-A5)


[1.58.51] — 2026-05-20

chore(docs): v1.58.51 — final housekeeping wrap of the v1.58.37 → v1.58.50 cycle (14 single-fix releases). No code or behavior changes; consolidation of the qa/ tree, doc currency, and the two lessons learned from this cycle. (1) qa/ reorg: all the version-locked source documents — FIX-PROMPT-CONSOLIDATED.md, FIX-PROMPT-FINAL-EXHAUSTIVE.md, FIX-PROMPT-v1.58.37_and_beyond.md, plus 6 root-level session snapshots (MASTER-REGRESSION.md, 2026-05-19-*.md, career-ops_*.md, FIX-PROMPT-v1.58.3.md, FIX-PROMPT-v1.58.4_and_beyond.md) — moved into qa/archive/v158-cycle/. qa/ root now contains exactly the 6 canonical perennial documents: README.md, REGRESSION-FINAL.md, UX-AUDIT-PROMPT.md, FUNCTIONALITY-CHECK.md, DESIGNER-EXPORT-PROMPT.md, G-005-closure-kit.md. (2) qa/REGRESSION-FINAL.md §13 documents every v1.58.37 → v1.58.50 invariant with its lock-test, organized by class (i18n / a11y / UX feedback / orientation / content fidelity / labels / tooling / docs) + 2 new “lessons captured” entries (markdown-bolded regex pitfall; Publish-runs-tests-against-tagged-ref pitfall). (3) qa/UX-AUDIT-PROMPT.md baseline table extended with the 14 new closed-in rows. (4) CLAUDE.md currency lifted (1.58.51, baseline 947/947). (5) .claude/PROJECT-CONTEXT.md repo-state + test baseline lifted. (6) docs/ROADMAP.md chain end now at v1.58.51 / Net 947. (7) README ×8 badges and content sync. Test baseline at v1.58.51: 947 unit (unchanged structurally) · 62 Playwright · 20 smoke · 23 comprehensive E2E. Also fixes the v1.58.48 / v1.58.50 Publish-failure pattern — by tagging at the same commit where ALL tests pass (instead of follow-up patches landing only on main). (housekeeping)



career-ops-ui v1.267.1 · page 7/11 · mirror of the project changelog for cvstart.org