From eea74f97ef582fead1fbe55303a4dd50a8039287 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 26 Jul 2026 02:26:45 +0200 Subject: [PATCH] feat: verfijn UX en borg Unraid deployment --- CHANGELOG.md | 18 + PROJECT_MANIFEST.json | 1027 +++++++++++++---- docker-compose.unraid.yml | 3 +- docs/ai/BACKLOG.yaml | 134 ++- docs/ai/PROJECT_STATE.md | 54 +- docs/design/STITCH_INTEGRATION_REPORT.md | 12 + docs/operations/UNRAID_DEPLOYMENT.md | 32 +- docs/quality/THREAT_MODEL.md | 2 +- docs/quality/TRACEABILITY_MATRIX.md | 8 +- scripts/deploy_unraid.sh | 37 + static/css/app.css | 1 + static/css/premium.css | 206 ++++ static/img/vacatureradar-docker.png | Bin 0 -> 7111 bytes static/js/app.js | 15 + static/js/login.js | 28 + templates/components/radar_illustration.html | 14 + templates/profiles/list.html | 20 +- templates/registration/login.html | 28 +- .../test_vr114_browser_and_accessibility.py | 10 + tests/integration/test_auth_login.py | 2 + tests/unit/test_deployment_configuration.py | 27 +- 21 files changed, 1440 insertions(+), 238 deletions(-) create mode 100644 scripts/deploy_unraid.sh create mode 100644 static/css/premium.css create mode 100644 static/img/vacatureradar-docker.png create mode 100644 static/js/login.js create mode 100644 templates/components/radar_illustration.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 2540ee7..290e85b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ Alle betekenisvolle wijzigingen worden hier bijgehouden. Het project volgt voorlopig een pre-1.0 semantische versieaanpak. +## Niet uitgebracht + +### Gewijzigd + +- Het zoekprofiel bevat een lokale interactieve radarillustratie met pointerparallax, een rustige scansweep, light/dark-kleuren en een statische reduced-motionvariant; de illustratie is decoratief en introduceert geen fictieve data. +- De Unraid-heruitrol gebruikt een gevalideerde SSH-hostalias met afzonderlijke deploysleutel en voert synchronisatie, build, healthwait en readiness-smoke als één reproduceerbaar commando uit. +- De cockpit heeft een premium “calm intelligence”-laag met zachtere oppervlaktescheidingen, selectief cyan, humane navigatietypografie en een duidelijkere componenthiërarchie in donker en licht thema. +- Mobiele kerncijfers staan compact in een 2×2-raster; vacaturefilters vormen een responsieve commandobar en de kanbanpipeline scrolt per kolom zonder documentoverflow. +- Dashboardkaarten, vacature-intelligence en het zoekprofiel gebruiken rustigere composities met minder decoratieve randen en beter gedoseerde elevatie. + +### Hersteld + +- Rechtstreekse SSH naar het Unraid-IP koos lokaal de verkeerde gebruiker en identiteit; het gedocumenteerde aliasprofiel selecteert nu reproduceerbaar `root` met de afzonderlijke deploysleutel. +- Inloginteracties laden nu als lokale externe JavaScriptasset; de eigen Content Security Policy blokkeert wachtwoordweergave en submitfeedback niet langer. +- Het Unraid/Dockerman-containericoon gebruikt nu een lokale 256×256 PNG via de gepubliceerde webpoort in plaats van de fragiele SVG-favicon-URL. +- De icoon-URL gebruikt een concrete server-side loopback-URL; Dockermans niet-ondersteunde `[IP]`/`[PORT]`-placeholders verhinderden anders het vullen van de icooncache. +- De deploymentregressietest controleert de volledige WebUI-/icoonlabels en valideert dat de verpakte icoonasset werkelijk een PNG van 256×256 pixels is. + ## 0.3.0 — 2026-07-22 ### Gewijzigd diff --git a/PROJECT_MANIFEST.json b/PROJECT_MANIFEST.json index 315b7eb..0ebd8e7 100644 --- a/PROJECT_MANIFEST.json +++ b/PROJECT_MANIFEST.json @@ -1,9 +1,9 @@ { "project": "VacatureRadar", "artifact_root": "VacatureRadar_Project", - "generated_at": "2026-07-21T21:03:35.829818+00:00", - "file_count_excluding_manifest": 323, - "total_bytes_excluding_manifest": 8496576, + "generated_at": "2026-07-25T22:17:50.331466+00:00", + "file_count_excluding_manifest": 448, + "total_bytes_excluding_manifest": 11127859, "files": [ { "path": ".agents/skills/vacatureradar-maintainer/SKILL.md", @@ -12,8 +12,8 @@ }, { "path": ".dockerignore", - "bytes": 175, - "sha256": "829f2621b4c38c469fc0f7eae36d480a50410f1a7ee2ec16fc53b2eb5b08cd79" + "bytes": 185, + "sha256": "678704de549f6e8f3420768dfad521a1cab98c1574936099549deb9882a9c610" }, { "path": ".editorconfig", @@ -22,8 +22,8 @@ }, { "path": ".env.example", - "bytes": 1965, - "sha256": "d28ed5cdbfb35fffddc5a1cf4397f76de73bc097e5b8641c37b32cbb34d39d57" + "bytes": 2823, + "sha256": "d6d6c96a379eb60be6929fbf014a0f8ebb2c877a7a66d06925ad96a28024981c" }, { "path": ".gitattributes", @@ -57,8 +57,8 @@ }, { "path": "AGENTS.md", - "bytes": 3904, - "sha256": "48df997b9301df9fe9e7228a8318f68fa03b23a5b4102f792d52c7b34ee4cf8b" + "bytes": 4521, + "sha256": "bba752f178136a2ad645e63cdf9f29ae4204cc6b246f2c202a4f9c8940b565a4" }, { "path": "apps/__init__.py", @@ -80,10 +80,25 @@ "bytes": 171, "sha256": "6d0080f6c363a8de33a3c8544729a87ad9cb444f09b075f5b97cfbd24074ba5c" }, + { + "path": "apps/core/auth_backends.py", + "bytes": 1758, + "sha256": "e62583ff633f83cd54a1cfeec3ffd70e5ddfbede031df2e358aad1bd6cf73d47" + }, { "path": "apps/core/context_processors.py", - "bytes": 250, - "sha256": "ed054ec1013257bfbcc65bbf57e4d951175a8a2daa00c1ac736089e46eb4beda" + "bytes": 539, + "sha256": "ce5a7c6102900254cb4fef52adb02286f46141aaef19b995312f9be5beeb270e" + }, + { + "path": "apps/core/entra.py", + "bytes": 3529, + "sha256": "9375df18938dd110994edd1b8f24376a829448e8940b387d6d0acf7146a339fb" + }, + { + "path": "apps/core/forms.py", + "bytes": 1085, + "sha256": "9227761c7952f64c9ea884a1e7710f9b4fcd867ccd5d758c1baf10cb48f8bcd2" }, { "path": "apps/core/health.py", @@ -105,6 +120,16 @@ "bytes": 3284, "sha256": "febf507ccae70502d141c2b2bb23b269353bdf158f7abe4c33f1e7bb91b8b5fb" }, + { + "path": "apps/core/services/__init__.py", + "bytes": 49, + "sha256": "123a8f79e4b0f03ff7e5093f14c90aaef0f58d2094a03623483f2e2d07d640e1" + }, + { + "path": "apps/core/services/automation.py", + "bytes": 4485, + "sha256": "50e02899faa7128a8658e634ed5be1583dc5259f3d76c42ad2e75527dae7e298" + }, { "path": "apps/core/urls.py", "bytes": 357, @@ -112,8 +137,8 @@ }, { "path": "apps/core/views.py", - "bytes": 4956, - "sha256": "a2eafc07898a94fa1c425fb2c39a86fd446261c98c67637f5ad5712bbcf0ccb0" + "bytes": 9503, + "sha256": "f354cb696d4d7ae47401dc131ae01364c1cc62b6cc7c8c3b77ee63322dcc233e" }, { "path": "apps/jobs/__init__.py", @@ -190,6 +215,11 @@ "bytes": 0, "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, + { + "path": "apps/jobs/services/activity.py", + "bytes": 5757, + "sha256": "b9fb83f1d6d3eb2193fcdc2a80f10cad8644435cb525a4f4e030ce05f817c93e" + }, { "path": "apps/jobs/services/ai.py", "bytes": 11893, @@ -197,19 +227,34 @@ }, { "path": "apps/jobs/services/applications.py", - "bytes": 13197, - "sha256": "70c94fc23bf1004a1d3517ac5ea065b0b5993a25b8f8decbf22a0eb83b9cada7" + "bytes": 14237, + "sha256": "0a923d61b0422901c8780c512617328b22d9b81e8dce46028bbc24f694ce6885" + }, + { + "path": "apps/jobs/services/cockpit.py", + "bytes": 6756, + "sha256": "0f69ab47a505682fe4aee9ca0adedafbe649404d6a7b7db40302317ee70d08a8" }, { "path": "apps/jobs/services/dedupe.py", "bytes": 3902, "sha256": "645834a7d59ecffa4a888702eebb03f20df79af5e30c8e6ed6ae4e9411ec0471" }, + { + "path": "apps/jobs/services/demo_seed.py", + "bytes": 8816, + "sha256": "c8f6f267734d3ae88e0486458d08afcc66e5aff6900ef3a45bd1b200194f63c3" + }, { "path": "apps/jobs/services/distance.py", "bytes": 1743, "sha256": "6e34bf7ba43d2a619f84d2d440a944ab704aad4d9b7075622dc8ce839d57e0b8" }, + { + "path": "apps/jobs/services/employer_intelligence.py", + "bytes": 3182, + "sha256": "4a1146a9d63e9b0aace983b8af2e69445c82b626134fb28318aa7d2ec969cf89" + }, { "path": "apps/jobs/services/employer_resolution.py", "bytes": 6203, @@ -222,8 +267,8 @@ }, { "path": "apps/jobs/services/feedback.py", - "bytes": 6212, - "sha256": "c0a56d313eefcb3da51a8e44fa11a1db740f0eb3a407b3f3d48a16b8d9ca131a" + "bytes": 6195, + "sha256": "8826d93162197d4f9282138d6eff556e156f80cdb2dea265d245b36326855119" }, { "path": "apps/jobs/services/geocoding.py", @@ -237,13 +282,18 @@ }, { "path": "apps/jobs/services/normalization.py", - "bytes": 8105, - "sha256": "446c3efdb25f5def8192ca7629c339b4f320f22228cc3c5095b9d25cb8c54ccf" + "bytes": 8430, + "sha256": "76e89a8ec12865d8d79b7bd88fa471e0a6a6285d2f6b45762fd292b9e6cfbb0f" }, { "path": "apps/jobs/services/pipeline.py", - "bytes": 12570, - "sha256": "e947d3d2a8a4ecb6e6c89d64758a48e1f0f8c2e29d0bf051b46fd6755b16dad8" + "bytes": 13366, + "sha256": "2f05fb3dbe6f8d96f801ff5a30c2ccad55d7b86a3cbc905912b6d8a24e8fc563" + }, + { + "path": "apps/jobs/services/relevance.py", + "bytes": 7363, + "sha256": "698b32ec6f20403d504abe120e5d2915f78a35b47d80c535ac1159167635b3ae" }, { "path": "apps/jobs/services/sanitize.py", @@ -252,8 +302,18 @@ }, { "path": "apps/jobs/services/scoring.py", - "bytes": 18515, - "sha256": "27927625f3055d47e5d2b92ab50862565f6e5596b7b128b779ee9ccb97814c2e" + "bytes": 19285, + "sha256": "71485e791d6a53439619a73e70a0e7b2bcd7ff6935f5fff595cd22e8f0c20f42" + }, + { + "path": "apps/jobs/services/skill_demand.py", + "bytes": 8688, + "sha256": "4b71982c6610d9323d3b129577ae8541294a66967d2c9d93eebae8c959e52b97" + }, + { + "path": "apps/jobs/services/skill_terms.py", + "bytes": 4200, + "sha256": "e1afb9cb7a48661d39cde61b4f64159be56a5386ebeb7c989ca79f5201c1e658" }, { "path": "apps/jobs/tasks.py", @@ -262,13 +322,13 @@ }, { "path": "apps/jobs/urls.py", - "bytes": 867, - "sha256": "9ddf60db95b5923193d3e2bbf12e364ff8c0e0e7b4269c737c9b74b8acde1e2b" + "bytes": 1370, + "sha256": "897ee04fab9749a31abdde67261b68da3bef2759729eaa033792ff3805013953" }, { "path": "apps/jobs/views.py", - "bytes": 6635, - "sha256": "445e29305e19589f2e3f05797bbfcb9f235cf1274ab0fbbd34c67b818c0943df" + "bytes": 15939, + "sha256": "c9701331f3138a1461ec130d06c41ba90724f0287b78efee606e1b725b86caad" }, { "path": "apps/notifications/__init__.py", @@ -332,13 +392,13 @@ }, { "path": "apps/profiles/defaults.py", - "bytes": 786, - "sha256": "e4cbad770f32cfe777371bd955b0fe5064656f6d8682a71374e1bb3eddf1f012" + "bytes": 758, + "sha256": "09c5e70badcad00a7ca92cb154752f1d865581fe04fc773031c20673d6bb16fd" }, { "path": "apps/profiles/forms.py", - "bytes": 3198, - "sha256": "3962b362c69b44ba920ef71a2198dd56eeae96bb49f18d1b9494eb0bbb4a9f38" + "bytes": 7301, + "sha256": "054e81f84543082129343826f1b9905297a52520ed4c967c31b4768ce698adc8" }, { "path": "apps/profiles/migrations/0001_initial.py", @@ -360,6 +420,16 @@ "bytes": 856, "sha256": "4ea855302b04288e7bc91d8c4ef67c472cd1a6ac268d0ea1ed9e2a8422af0373" }, + { + "path": "apps/profiles/migrations/0005_searchprofile_experience_years.py", + "bytes": 585, + "sha256": "2e79c45d58026a11476760a24fd1b2f705a7f986b2227dafc8f89ac113b9c4f1" + }, + { + "path": "apps/profiles/migrations/0006_remove_seniority_score_weight.py", + "bytes": 674, + "sha256": "ec9606e2e030d96ebfb1831f3e7994cdec76d8f7053a391fba30e6219ad28f5f" + }, { "path": "apps/profiles/migrations/__init__.py", "bytes": 0, @@ -367,13 +437,18 @@ }, { "path": "apps/profiles/models.py", - "bytes": 7110, - "sha256": "2812a1d6a7ca24b94384d85774fb7bc0cc2b760990fcea9914f07301f78bdd05" + "bytes": 7510, + "sha256": "3d03170de26153b81533b10ecbede8e4aaa9d0463cd45fbbaaa400d23dbb70c1" }, { "path": "apps/profiles/services.py", - "bytes": 1267, - "sha256": "ec07fa9a4a7ad0b448f294b72e0c1792fc9eb775c12586174f3ec6d2953d6b58" + "bytes": 2209, + "sha256": "1fe5c183d8f43b86d77a1645e69e75cedb10018a1838e544f233509a4542c6ff" + }, + { + "path": "apps/profiles/taxonomy.py", + "bytes": 10964, + "sha256": "5a046f87c1765001adf7ff957f023794f9c13674bc252b6c612864148119cc3f" }, { "path": "apps/profiles/urls.py", @@ -382,8 +457,8 @@ }, { "path": "apps/profiles/views.py", - "bytes": 1580, - "sha256": "1acbf03976ec5dee81fe74c542bd12c21a4ddc69a659c893fd0fc63105b56097" + "bytes": 2884, + "sha256": "53926cfc378518fd5187321bfbe52d45405077417da1474a0e440cea376add04" }, { "path": "apps/sources/__init__.py", @@ -392,13 +467,13 @@ }, { "path": "apps/sources/adapters/__init__.py", - "bytes": 555, - "sha256": "d29e30ad41f4c43a98eb8c9285dc97590ab2f75e1804e72ee486538b9c0a1535" + "bytes": 792, + "sha256": "3014318fe05d5285d0bb496344b22f78a146f3831d4fb432fdf28cd9e7e3afca" }, { "path": "apps/sources/adapters/ats.py", - "bytes": 18142, - "sha256": "e43a8f4328aa4affea6e6cefe0e15d398e0b98e0850244186efb4f306178bd59" + "bytes": 19701, + "sha256": "650e28e7f7c4bc9a09bdaa4c6acacba765c40d78b3b4e869bb6e3e306b5084b5" }, { "path": "apps/sources/adapters/base.py", @@ -407,8 +482,8 @@ }, { "path": "apps/sources/adapters/email_alert.py", - "bytes": 4077, - "sha256": "3f82dd60ced360f2dff0e408984d12981024695bc5862c1e12aba0e625b63069" + "bytes": 6160, + "sha256": "eff073cb2664a326d34585d831322e80343ce5bb53d920fe20b40d05bf727d0e" }, { "path": "apps/sources/adapters/generic_html.py", @@ -420,10 +495,25 @@ "bytes": 6905, "sha256": "f54d1a704e8157a05e9faf37532048bede4f3f874aa12e9351ee632e64f873e6" }, + { + "path": "apps/sources/adapters/kempen.py", + "bytes": 11883, + "sha256": "b548a8dc36133bb3b9632f594b071cc9ce3f413af55181a3a640e6aac2493681" + }, + { + "path": "apps/sources/adapters/mol_region.py", + "bytes": 10088, + "sha256": "c45af1fa08aab08cc9aa7bda28c4f62dbdc853367ce694dfa7c536a0aa850abd" + }, + { + "path": "apps/sources/adapters/regional.py", + "bytes": 12285, + "sha256": "b347dc4275bfead48175a69c0f86667da5049cc488dbf45c1a6c83d83c7d4ea6" + }, { "path": "apps/sources/adapters/registry.py", - "bytes": 2128, - "sha256": "85042cb30be58ce6170b5c380f22f4377997ac71cc5b9ef55ca9a7bbe315a98a" + "bytes": 2657, + "sha256": "ce85f6f1ddd06049f814fcd4b03e04b940600b173fe72e3748ec1494e6f26f7f" }, { "path": "apps/sources/adapters/rss.py", @@ -432,8 +522,8 @@ }, { "path": "apps/sources/admin.py", - "bytes": 2800, - "sha256": "06a6b41d1d85a41f18e7f628c5284c9530656b7f69536206ac689123b65c7d35" + "bytes": 3238, + "sha256": "2b434ea249f245bde84ac435df6992ed7c9cdeaa05b4334812fa8e31c53dd0f0" }, { "path": "apps/sources/apps.py", @@ -442,8 +532,8 @@ }, { "path": "apps/sources/forms.py", - "bytes": 939, - "sha256": "b58a0f824afa808e853194899c98b73d6c2758a0d283b22ac4e182367d3a12fd" + "bytes": 2702, + "sha256": "efe29da49e7df623606d1d5e91507f90e9d9c9231eb80a2828ec1f835683016c" }, { "path": "apps/sources/management/__init__.py", @@ -463,12 +553,17 @@ { "path": "apps/sources/management/commands/import_job_fixture.py", "bytes": 2734, - "sha256": "d54a259ca715b97c5a5474c9f60fb9538eefb3ad0f5c2462880280a6f1c8495f" + "sha256": "902d92981e4f2a965574d3e810ba789395c91dd6005a784820101179477e5dae" + }, + { + "path": "apps/sources/management/commands/rotate_mailbox_credentials.py", + "bytes": 600, + "sha256": "1036b74b80ca85685fc393dd0d51a72a7632a42e870386fd2fe98de6bc5ecde0" }, { "path": "apps/sources/management/commands/seed_sources.py", - "bytes": 2209, - "sha256": "22ac3a78af62215f531f18f2473e8982b4efa94b936a606c9686ea5c29657fce" + "bytes": 5413, + "sha256": "b1e3db466690c600f0b599444d4f790c8543157525cecafd9860669905b2518a" }, { "path": "apps/sources/migrations/0001_initial.py", @@ -485,6 +580,26 @@ "bytes": 1770, "sha256": "2125f2c6566298d6f15c86256d5598ea31fc113db6ad8597d0e924c4d0ab3e23" }, + { + "path": "apps/sources/migrations/0004_remove_source_unique_domain_source_type_and_more.py", + "bytes": 632, + "sha256": "5828a3437e126480d8e5c4b41a8d7090ee2e7d82f030521fad11fc5baf1e307f" + }, + { + "path": "apps/sources/migrations/0005_alter_emailmessagerecord_message_id_and_more.py", + "bytes": 3787, + "sha256": "e50d2af15c743cf10599c2f9624a49a08e7c2a941f5b0be3f20ec3da19efb916" + }, + { + "path": "apps/sources/migrations/0006_alter_mailboxconnection_platform.py", + "bytes": 643, + "sha256": "bf1b52d122db45730e0ce8928fb3bc0d1a026a17d6600f3728d93dff4e781b3d" + }, + { + "path": "apps/sources/migrations/0007_alter_mailboxconnection_platform.py", + "bytes": 997, + "sha256": "bfee99ce105de7cac54be040b3283a39b50746b04b39044520ff3dca1b315ffe" + }, { "path": "apps/sources/migrations/__init__.py", "bytes": 0, @@ -492,8 +607,13 @@ }, { "path": "apps/sources/models.py", - "bytes": 12887, - "sha256": "781d9e379904a82a4b8bba356d7835ba2f5b0188528e9d9848eede8c1369e017" + "bytes": 16978, + "sha256": "eb63286446242b22ce21e590c8b7cee0aea7cc09609075ee0ed86dee964ea6fb" + }, + { + "path": "apps/sources/platforms.py", + "bytes": 2645, + "sha256": "b6574522bc83225b8efb3a92d20bfb855349b6300b725e513aac9090eac57d22" }, { "path": "apps/sources/services/__init__.py", @@ -507,23 +627,33 @@ }, { "path": "apps/sources/services/discovery.py", - "bytes": 12641, - "sha256": "da9b31af171a67e428310430fd1ed596be186c82cade5261022fbb4c807056ff" + "bytes": 12819, + "sha256": "30821023e262833ad34522d951953f0b4bea77d59948d698bd1fd818719f50ee" }, { "path": "apps/sources/services/email_import.py", - "bytes": 3767, - "sha256": "e05a370aed9015c3f3d8a2968fed5a68470fa0ed3bd41d8d5ad151836b29ac61" + "bytes": 4846, + "sha256": "2f4bda81d6b345225a533ec2ea2aff98eee0617d5d3568a340b86a5deafdc43d" }, { "path": "apps/sources/services/fetcher.py", - "bytes": 6335, - "sha256": "be99236dc8ce3ecc0f3c262a672f3a97088a3b767a143f5daa01652cc1e4d51f" + "bytes": 7205, + "sha256": "3c16e3409b2d118443552e84d0310131ac0987e8b3e73a9689e72d509447ffb8" }, { "path": "apps/sources/services/health.py", - "bytes": 11635, - "sha256": "62fb603868b12f1464502f1ef1fef08992b454b5cf522dc8b570fa481c3d70be" + "bytes": 11751, + "sha256": "cf659d2d722ceac5b8a32a1b6dc4bc2c19351929965d922f7195cb7b264b4703" + }, + { + "path": "apps/sources/services/imap_import.py", + "bytes": 3336, + "sha256": "53f07f4ceca4ed45e037f44cd6586e605112748c11c22402d8c4095b828c368f" + }, + { + "path": "apps/sources/services/mailbox_connections.py", + "bytes": 4963, + "sha256": "86f5f1b943dc0bfc7f77005ee2fa1ed751b137105a170d37028453589b821877" }, { "path": "apps/sources/services/manual_import.py", @@ -532,19 +662,24 @@ }, { "path": "apps/sources/services/policy.py", - "bytes": 4294, - "sha256": "b07c5a62c3483fe3dc320a32cd67fcc8f4a717b275c336f3cba8ea7beb2fd295" + "bytes": 4372, + "sha256": "d82a9758070a420b6d6bbc521149e545ae9641b20841e845f524ef7a947c0f7e" }, { "path": "apps/sources/services/robots.py", - "bytes": 8869, - "sha256": "fb46e645d0f6d8c45205f0cd45a55e07e9ebb255fcefea1866574f800b314d5c" + "bytes": 9705, + "sha256": "16d8d79a6e4bea1d370393f4bc3ec1cf8d1495df2bd190b1e2687ff1496ee70f" }, { "path": "apps/sources/services/scheduling.py", "bytes": 5329, "sha256": "13e059faf4668176b6ba25ebf3e5332eb7b012f40ad0e3af033b0125a3b46e25" }, + { + "path": "apps/sources/services/tls.py", + "bytes": 473, + "sha256": "2b2e4388be47628a579a5394515e074acbe8eeccdca365234172395732d2ddbb" + }, { "path": "apps/sources/services/url_security.py", "bytes": 2423, @@ -552,23 +687,78 @@ }, { "path": "apps/sources/tasks.py", - "bytes": 10101, - "sha256": "85691e14dd10a4f9545fa3e13dc4f79a5ae5bfd3ec2ceacae335e1f3b4f9a9a4" + "bytes": 10772, + "sha256": "5e3d0141b0782040877b21a5db6c864d7b8266971441640cd63c7696549b85c1" }, { "path": "apps/sources/urls.py", - "bytes": 375, - "sha256": "1c6bc7029f7af2e4a3ee0bdfb8a4d933b2b518e66c7ac18dd9c2e04d11df9d8d" + "bytes": 781, + "sha256": "cece4e8678b2cdd097dd97e21ce78b58f3b1ae5cd77e99ebab69d17418cfcd49" }, { "path": "apps/sources/views.py", - "bytes": 7713, - "sha256": "e1f4cadddadae4daac34b12bc595ba6ab82cd8e43d45dc60aa0a3034e34d9d2e" + "bytes": 12698, + "sha256": "2b8c0f14aa4a82e88b7ec22c3dd340f4f72748cde1169ad663a5c7514361d6dc" + }, + { + "path": "artifacts/visual-validation/activity-desktop.png", + "bytes": 74942, + "sha256": "67cdc9a1af50835377f4c808168c120a5e2833ee2895f2abf16eaa4f1ad7068b" + }, + { + "path": "artifacts/visual-validation/applications-desktop.png", + "bytes": 75943, + "sha256": "02b869c6187f3fea4c54fac749fedfdcb69afad943f77b7e1f9155f60af6304c" + }, + { + "path": "artifacts/visual-validation/automation-desktop.png", + "bytes": 98909, + "sha256": "de0de905798ed101091ee195dd647596d6bbb3bd562825b3a7db9a071fbaadd2" + }, + { + "path": "artifacts/visual-validation/dashboard-desktop.png", + "bytes": 112679, + "sha256": "f5685e202f96247250504cac14453fe2e75f1b2d283fa62a11b908cf6b9f0ec5" + }, + { + "path": "artifacts/visual-validation/dashboard-mobile.png", + "bytes": 30886, + "sha256": "770b2f6e86be503886e98816844df5b4634b68958f9d8ed52589bbcea8600ce5" + }, + { + "path": "artifacts/visual-validation/employers-desktop.png", + "bytes": 93351, + "sha256": "8b3809fa664d7a45326ddff8e3cf8134141814145817dc9eb16755246b609e63" + }, + { + "path": "artifacts/visual-validation/job-detail-desktop.png", + "bytes": 106710, + "sha256": "fff3533dc9e73c95e32f50098609f27f875f24e345f5f20558efa9aec0073418" + }, + { + "path": "artifacts/visual-validation/job-detail-mobile.png", + "bytes": 34132, + "sha256": "e62baa094c826143ee1d02cfc39b87bdd47b6cb3144a9944d10e97826e671bfa" + }, + { + "path": "artifacts/visual-validation/matches-desktop.png", + "bytes": 103940, + "sha256": "2d6b141aaa050bc56ba3201d9a057a6aaf3f479d96cddec3437707dddc9603f2" + }, + { + "path": "artifacts/visual-validation/profile-desktop.png", + "bytes": 111149, + "sha256": "1b3bb7b2740f0fda433ad5a54cad75c1338305ed73728fdf8a14af0a2def34d0" + }, + { + "path": "artifacts/visual-validation/sources-desktop.png", + "bytes": 102625, + "sha256": "a646c89834c6374e230763bcdb548a721c7017efb1bc57fed3fad5deb789c557" }, { "path": "CHANGELOG.md", - "bytes": 2157, - "sha256": "163aec745cb3326f6fdf651175ba6a94a3e1d9a526962956e3094feb153ca42e" + "bytes": 18562, + "sha256": "583272f45ba2204440d3f015afc022fe3658754d98b7e4862f69bc7d570a5961" }, { "path": "CODEX_START_HERE.md", @@ -592,13 +782,13 @@ }, { "path": "config/settings.py", - "bytes": 12928, - "sha256": "ec4b781092db54821b65ca534b0a12916413ebfbbd283df480b3d954166b4927" + "bytes": 14762, + "sha256": "d854e12cc85f763af1222897abd7c6c93d3c8fbee40b2153841838cdb91ba9fe" }, { "path": "config/urls.py", - "bytes": 941, - "sha256": "b472b24a0213fb020add183215b9c4ea7107d89b4fd8db22f4aa17aff727f5a0" + "bytes": 2141, + "sha256": "4654aaf5c4ad2aa842173dfa0ecc78f991e623df4fd43aae855a680003adbf46" }, { "path": "config/wsgi.py", @@ -607,8 +797,8 @@ }, { "path": "config-data/geodata/README.local.md", - "bytes": 453, - "sha256": "f98c437ea3437ca1419c9a739864b7f3ff5938c33121128337355db0cdf7c6b3" + "bytes": 2122, + "sha256": "a7d970a0f19d6b219b95d65ff804d35d5e82dfab4d94299568217cc558cc34f8" }, { "path": "config-data/profile.example.yaml", @@ -617,13 +807,13 @@ }, { "path": "config-data/seed_sources.yaml", - "bytes": 434, - "sha256": "0c1a4086f3f793011d921812b852bcbc69b06aba8aeece5ffe5f662e9b7dc320" + "bytes": 35432, + "sha256": "1bf3861661ba1c8777ea2d1b02814424c7779fe1200a72f9c403ebea8dd20628" }, { "path": "config-data/source_policy.yaml", - "bytes": 450, - "sha256": "616e2bd584d70812b87bd4129900f87bececc9d331815530e57b538711e6536d" + "bytes": 506, + "sha256": "61230414c78326743cf586f686193d05bf5149c965e449b27b8827cf94524936" }, { "path": "CONTRIBUTING.md", @@ -632,13 +822,18 @@ }, { "path": "deployment/unraid/README.md", - "bytes": 394, - "sha256": "127cc2f9f3ce5a33b4388d247a19bc0681b4777b7afc3a4192a8817134e4b3f3" + "bytes": 506, + "sha256": "ec2c762811ca11d8daba3677e986b22a82c3e93123b8c69411a7b297c5511c4d" + }, + { + "path": "deployment/unraid/supervisord.conf", + "bytes": 1469, + "sha256": "b4936b4a5c05a2a8d6631956d810eec1fff5168afa9c774b46628ea2a52e4a3c" }, { "path": "docker-compose.unraid.yml", - "bytes": 2255, - "sha256": "2cfb47cabebf2bde71aa859239c99a771502773d853b48b1c735feb66c64e797" + "bytes": 1541, + "sha256": "25a2e6bf6abb3f4dd6bfc6ba0e53f6726ed11c09a3a15ed9f0a5034a56fba740" }, { "path": "docker-compose.yml", @@ -650,6 +845,11 @@ "bytes": 1203, "sha256": "a231c7f0872c8a6fe8c2fe3bac46479e1b8ce3ec419fb3cdfadb25b541386ec2" }, + { + "path": "Dockerfile.unraid", + "bytes": 961, + "sha256": "d71824ea8497dcdafb5e140e914ef15401d3966fa8d47e3dcec269b1cfb10884" + }, { "path": "docs/ai/AUTONOMOUS_EXECUTION_PROTOCOL.md", "bytes": 4668, @@ -657,8 +857,8 @@ }, { "path": "docs/ai/BACKLOG.yaml", - "bytes": 46387, - "sha256": "52f5db602dfb6c83e38e8668520107c2c360d3ba9702a6ad537b5d62aa0aa71b" + "bytes": 81845, + "sha256": "4a2fb563da45cdcbdb57b36968a9f13da7372370fbcd12b2432e49b62cc1adee" }, { "path": "docs/ai/CODE_REVIEW.md", @@ -677,8 +877,8 @@ }, { "path": "docs/ai/PROJECT_STATE.md", - "bytes": 11335, - "sha256": "b172145378da83600528253aab0862deef3cee3db9b121aed65cbb40da30cab4" + "bytes": 35480, + "sha256": "63656724c7f86cc75f8d400939445233c8b926fed22f902fdfd293210fe8593c" }, { "path": "docs/ai/SESSION_BOOTSTRAP_PROMPT.md", @@ -717,8 +917,8 @@ }, { "path": "docs/architecture/adrs/0005-email-alerts-as-platform-ingress.md", - "bytes": 697, - "sha256": "fc5f864ef90e168ed1f38f999903d601c5aff3b6e9882063ef38fbb0dec972f5" + "bytes": 2081, + "sha256": "3c21504408b588acdcb600a5b71e02448976a8aa5bab59eb308792f80faa4617" }, { "path": "docs/architecture/adrs/0006-no-automatic-applications.md", @@ -735,6 +935,11 @@ "bytes": 639, "sha256": "790be11bc672536a518b7fc7c9d849054f1831cacbe97a41b9546903d8cdebcd" }, + { + "path": "docs/architecture/adrs/0009-encrypted-platform-mailboxes.md", + "bytes": 2160, + "sha256": "0c2bf6d00a2c603dd205b06af629e7d8f5d3d9190bcd2b4af765f877f0b9dd71" + }, { "path": "docs/architecture/DATA_FLOW.md", "bytes": 3372, @@ -747,18 +952,18 @@ }, { "path": "docs/architecture/SCORING_ENGINE.md", - "bytes": 4755, - "sha256": "9e464c4d1ecb80085af8e214c7d4f2d61a1330233dc5146a19139cdb9c91876d" + "bytes": 5445, + "sha256": "4a5fbb1e7ff555659c88a792464f00b4594808b47d605cc750bdc78537d64cd9" }, { "path": "docs/architecture/SECURITY_ARCHITECTURE.md", - "bytes": 3548, - "sha256": "193c2a62680bc42e0aefd8bbaea964d47282ed4f41ffe4ef93ad728d808692f9" + "bytes": 4166, + "sha256": "868c258928cc8d2b85fd3deb84bf49cd1fcc12d926bd4a1d4f14deb8b635f260" }, { "path": "docs/architecture/SOURCE_ADAPTERS.md", - "bytes": 5499, - "sha256": "dcc2dd5b47d1119b4b91b2231bebcfb9b67aa8777d0ecdb9891b87ca77d4b8d2" + "bytes": 7445, + "sha256": "99f0c0009ef9b32cb78608a27b9e735111e5eed97ac440d05a85e083e64d825c" }, { "path": "docs/architecture/SYSTEM_ARCHITECTURE.md", @@ -767,8 +972,8 @@ }, { "path": "docs/design/ACCESSIBILITY.md", - "bytes": 2850, - "sha256": "8e57f0d558cda5efad6a1af8fc227e68096a9eb423c5cb966a88786436d3da00" + "bytes": 2799, + "sha256": "103929b971920c85b70328a5e4190dd2c9ae6ddadbacf8ce7793dff8e237a4c9" }, { "path": "docs/design/dashboard-wireframe.svg", @@ -777,8 +982,8 @@ }, { "path": "docs/design/DESIGN_SYSTEM.md", - "bytes": 4168, - "sha256": "5454b8ba4d0238ea4683497b6fcbc41f980f7aa7e4f2a563ccaa78a1b92c4a07" + "bytes": 4103, + "sha256": "345b508ea0cc86ed7e64d7f82847991deb3ac6a35c99ee607b608a2576d81d79" }, { "path": "docs/design/stitch-reference/activiteitenlogboek/code.html", @@ -875,15 +1080,25 @@ "bytes": 331989, "sha256": "4a49d9d1c9c7249465ddd24f5d64ba5957592eb2a4d9e76eef4f851a8c15ce6d" }, + { + "path": "docs/design/STITCH_FRONTEND_CONTRACT.md", + "bytes": 4417, + "sha256": "eb5268b70b046c2b28a1535da21e80570c30b9e795fc7878923118f68d5a194c" + }, { "path": "docs/design/STITCH_INTEGRATION_PLAN.md", - "bytes": 5515, - "sha256": "25f2d62598f2d3535e32b7931e688f97425396ea517215eedfd12ea36995a8ce" + "bytes": 8546, + "sha256": "ea23ff53bceb27803c56ad98baa896e92faf9f97be9ff28446e93ef4c4a3f31e" + }, + { + "path": "docs/design/STITCH_INTEGRATION_REPORT.md", + "bytes": 7007, + "sha256": "ed0b206057c5be278c5ff514fe9b62bd2a08e787ce20b94678eab50d7e659db9" }, { "path": "docs/design/UX_SPEC.md", - "bytes": 4805, - "sha256": "7df8ba1a2f31be9237725c920e6a36d1441a11ba1366d3bec70b5d1e95e9b5eb" + "bytes": 3992, + "sha256": "ee1865bb3efae5943952098c42b505547d78b74a4268c44dfe13e3e84a3a5bb2" }, { "path": "docs/design/validation/applications-dark-1280x800.png", @@ -965,6 +1180,66 @@ "bytes": 74148, "sha256": "545febbbce38cab784bff11be4c78e4e608f71c424fbc8271013270305f13c84" }, + { + "path": "docs/design/validation/VR-119_REPORT.md", + "bytes": 1254, + "sha256": "e71d3733313a4c8c932dd15fb308d5b1e5618ee003ad22f05d2c5ce21d871edc" + }, + { + "path": "docs/design/validation/VR-206_AUDIT.md", + "bytes": 4671, + "sha256": "50b6dad9d45bcca775fe4d47f50b5f0ab031cca894c02280af5ebbdffc5e99d7" + }, + { + "path": "docs/design/validation/vr119-applications-dark-1440x900.png", + "bytes": 74492, + "sha256": "aea768870b55fe622cf1cb1f6903e1c1f5a458afcfabbdd18d33215ce204fe9b" + }, + { + "path": "docs/design/validation/vr119-dashboard-dark-1440x900.png", + "bytes": 101649, + "sha256": "078856aa81af8961439f294d630748c4848f4de116df3b4858ae1b64a8a681a2" + }, + { + "path": "docs/design/validation/vr119-dashboard-light-1280x800.png", + "bytes": 92682, + "sha256": "adde2c233f486f78d2490c72eface20306e585d741fc7bc40d317edd502be49f" + }, + { + "path": "docs/design/validation/vr119-dashboard-mobile-390x844.png", + "bytes": 28024, + "sha256": "e0f2745db4290006b87f56625ac554856b17cd9b792b0b955aaf9fcd7a7aabb3" + }, + { + "path": "docs/design/validation/vr119-job-detail-dark-1440x900.png", + "bytes": 105636, + "sha256": "30f1fdcd2e4897c994fd3aa4788d77997f3a6ab40348bfd2a093c1614ecec25a" + }, + { + "path": "docs/design/validation/vr119-jobs-dark-1440x900.png", + "bytes": 86196, + "sha256": "b51e6cbbcbb0b1463acd8a94992900d55b04212878668331c43ae401c11b0190" + }, + { + "path": "docs/design/validation/vr119-mobile-navigation-390x844.png", + "bytes": 21960, + "sha256": "88c9bed22130615db791106c0bbd86df4d56b1e2fa21cc3c9584e2286b084094" + }, + { + "path": "docs/design/validation/vr119-profile-editor-dark-1440x900.png", + "bytes": 90513, + "sha256": "7b05eb49caa1cc617951c9782baeca686e61abf521fb8e65abd816d4b88d2975" + }, + { + "path": "docs/design/validation/vr119-sources-dark-1440x900.png", + "bytes": 87288, + "sha256": "2a475295f74feb32312c6f315152551f2615a0df7240447abdf644fd18180a08" + }, + { + "path": "docs/design/validation/vr119-system-dark-1440x900.png", + "bytes": 74142, + "sha256": "edca2718bc18cb702caa8d01023a71aeaf68538c8d2042daee56f9c22fb69d9e" + }, { "path": "docs/design/WIREFRAMES.md", "bytes": 5678, @@ -972,19 +1247,24 @@ }, { "path": "docs/operations/BACKUP_RESTORE.md", - "bytes": 3482, - "sha256": "c1b46c266a99ca60703271c2de3ff702112d190685d5c5f310a15ca008a94fb4" + "bytes": 3670, + "sha256": "d4457f5c09a901e91d6d7c773aac6181d550b3347e872978536fcdabbd9c3925" }, { "path": "docs/operations/GEOCODING_DATA_IMPORT.md", - "bytes": 1026, - "sha256": "ed88233e0df929fbdb9ca2db4bf72b6bc2fbc44c0a08129a0f69563cfafc95ec" + "bytes": 1503, + "sha256": "2aa4e8a01ff1f5f0ecf42dea15aaa55fc9ec29eddba5b2fddfd6a917473f845b" }, { "path": "docs/operations/GITEA_DEPLOYMENT.md", "bytes": 1300, "sha256": "861ce84bec62ffafe30605c7e78b97a51ad808b55524f206892dd9615d79a1d2" }, + { + "path": "docs/operations/LIVE_SOURCE_CATALOG.md", + "bytes": 8627, + "sha256": "804dfb3b6d453ccc832032ba00edb5f14181afddb7980cd0fcdfeb9a3610ca86" + }, { "path": "docs/operations/LOCAL_DEVELOPMENT.md", "bytes": 3281, @@ -992,18 +1272,18 @@ }, { "path": "docs/operations/RUNBOOK.md", - "bytes": 5698, - "sha256": "dded9b921c597af6da54f58e963a642614439ae7c364f8d2254efa79dcc73ceb" + "bytes": 6922, + "sha256": "8d00cc0d9f00429f2923e0efa0fab6a8fb55b24cdae7c37cd49f39c278608ccf" }, { "path": "docs/operations/SOURCE_ONBOARDING.md", - "bytes": 6384, - "sha256": "4a63d376242d20a57bdbbac94ed14caec8df5df42a9e718dadf6f9a1483fc291" + "bytes": 10000, + "sha256": "cf2043d1586a0bb2a1387cd7a07e1df1dbafbe9762291300c91406881723164e" }, { "path": "docs/operations/UNRAID_DEPLOYMENT.md", - "bytes": 6203, - "sha256": "adbaeceb905976df3af0442f55a19c8061bcd187e6b25aa4b14d3eb271852be8" + "bytes": 7958, + "sha256": "95a27b670ffb5d64cec30cc8f300dc7d6ad4ae616f39ab96ddf8b4122b554d36" }, { "path": "docs/product/ACCEPTANCE_CRITERIA.md", @@ -1012,8 +1292,8 @@ }, { "path": "docs/product/PRODUCT_REQUIREMENTS.md", - "bytes": 9386, - "sha256": "50a115d7c30a49a47aa935b7e608b515c3d8b8806ba2a4971394b486abe7be5f" + "bytes": 9506, + "sha256": "5accef7bc09662768a03ec38d344493362ed575cee7dce0fb7cf1438806ad31e" }, { "path": "docs/product/SCOPE_AND_NON_GOALS.md", @@ -1047,13 +1327,13 @@ }, { "path": "docs/quality/THREAT_MODEL.md", - "bytes": 10944, - "sha256": "6026b39f011bc03198607d73d3674a03421ea8b2ca302f730286bf104f404d3c" + "bytes": 15196, + "sha256": "ea040b20aff44671fd5da6572e05b6b7629340defb48d511b9e94690a66247f5" }, { "path": "docs/quality/TRACEABILITY_MATRIX.md", - "bytes": 8311, - "sha256": "9037f3010b06a73c1a42645a2735655fe4a01f4a9cc7613313ff6a9e58cad746" + "bytes": 14114, + "sha256": "6b61eba9d17acd3ef1ffab1c76b7d05f20e5b076ecb18aa3105bc19c2920d94a" }, { "path": "docs/README.md", @@ -1100,6 +1380,11 @@ "bytes": 807, "sha256": "b4b995b74148a8854a14534c47df1cdc194b2951b4a11630c35065eabbd51d4d" }, + { + "path": "fixtures/ats/lever-public-postings.json", + "bytes": 464, + "sha256": "da721279c596b75e052983a74e218fc665aa3c3c11af9c9c98ec46d6f52bbd30" + }, { "path": "fixtures/ats/recruitee-detail.json", "bytes": 426, @@ -1110,6 +1395,11 @@ "bytes": 702, "sha256": "30f13caa9a2a0319e356e1c92ec242858a44a22f10327bedd9761c7c56f174d9" }, + { + "path": "fixtures/ats/recruitee-public-offers.json", + "bytes": 566, + "sha256": "b1c5d55115fa18fc3f081004f5a614827a33bcb61c42acd388a239fa17bdd384" + }, { "path": "fixtures/ats/smartrecruiters-detail.json", "bytes": 432, @@ -1120,6 +1410,11 @@ "bytes": 757, "sha256": "1274332f42afdf6949f6aea6bd1e60d7f68adca7bbb672f6a753ab4a8de33b34" }, + { + "path": "fixtures/ats/smartrecruiters-public-postings.json", + "bytes": 1128, + "sha256": "f1bdac89b86872e57b83c66f062863a8c5dd91fe933d5607f2ef1af03fc649ef" + }, { "path": "fixtures/ats/workable-detail.json", "bytes": 447, @@ -1155,11 +1450,51 @@ "bytes": 311, "sha256": "f5aa7edc8a0b910868a53ef66732851386e8ee70bc960a05206f56d69ee3f977" }, + { + "path": "fixtures/emails/careerjet_job_alert.eml", + "bytes": 537, + "sha256": "4b9f24699a6ee6195e9d3fb1f0448581e898ab04cc00b32c9455a9ec760bda7d" + }, + { + "path": "fixtures/emails/ictjob_job_alert.eml", + "bytes": 575, + "sha256": "4347ea6df02d45cffda2fa35bf73c6c7e478897b79cec7b85347fca76101db9c" + }, + { + "path": "fixtures/emails/jobat_job_alert.eml", + "bytes": 511, + "sha256": "ffe666b227cb05cd0d660f7536cbdf6455172f0823b84475dc662f097d3e197e" + }, + { + "path": "fixtures/emails/linkedin_job_alert.eml", + "bytes": 687, + "sha256": "53b8e48957f160fb2bd2099fcaed2bfbdbf8e8e9483347af512372cb49befc5c" + }, + { + "path": "fixtures/emails/randstad_job_alert.eml", + "bytes": 581, + "sha256": "0239eaa6afaaffc550673304d409a049c370d899a51e64511bdcb0cdb9a99409" + }, + { + "path": "fixtures/emails/roberthalf_job_alert.eml", + "bytes": 594, + "sha256": "3fa8223ea307d02d9e50725931af2f6f4611579f3b96752317a97151b11a731f" + }, { "path": "fixtures/emails/sample_alert.eml", "bytes": 1033, "sha256": "41bb5f7ee4a17e9918dc0e15d3c1bc72d189091cc6124d0dacb4c91c0c77fa36" }, + { + "path": "fixtures/emails/stepstone_job_alert.eml", + "bytes": 594, + "sha256": "bf3d276c895039a8ecd6c99eb03b87df6c6c7ab150b332e723f12af6906a765f" + }, + { + "path": "fixtures/emails/vdab_job_alert.eml", + "bytes": 977, + "sha256": "fd9d7d7e89b6b06565d351a04da92e1d770d4971747c04bfb4a3e1123ea19984" + }, { "path": "fixtures/feeds/sample_jobs.xml", "bytes": 444, @@ -1170,6 +1505,46 @@ "bytes": 405, "sha256": "e4f086a7cbd01dc249cd73c7970d55667a7720e63fe2455c1f9de91cd06b9ae8" }, + { + "path": "fixtures/pages/aca-group-jobs.html", + "bytes": 285, + "sha256": "71f76d2878ed820798640c5da40e6f78d2f39555fbc6d495b98a166392f08633" + }, + { + "path": "fixtures/pages/cipal-schaubroeck-jobs.html", + "bytes": 488, + "sha256": "26c0ccba6dfc486c89741d5acd7e95f1c9ce0dc5f0180bff160a228ee2a6196e" + }, + { + "path": "fixtures/pages/corda-campus-jobs.html", + "bytes": 772, + "sha256": "fed43d3dfdac625d613b7ca210ec008ce2b37f168b1d4441403dcf5993dd2c58" + }, + { + "path": "fixtures/pages/daf-westerlo-jobs.html", + "bytes": 599, + "sha256": "b5b5ce9cbb5f1e9741251cc3e01d66364e6dc66875579a4df41dad697cc4f97b" + }, + { + "path": "fixtures/pages/netropolix-jobs.html", + "bytes": 349, + "sha256": "9da67a84d74cdbe8af1a7b0f88d6e46c7ff5a7bf2b08a381bf3878b09d96d1ee" + }, + { + "path": "fixtures/pages/pxl-jobs.html", + "bytes": 437, + "sha256": "8decb00a0595b6169d29ecfdd9fa1b8866037efaa5a319df280e08d4460402f0" + }, + { + "path": "fixtures/pages/ravago-kempen-jobs.html", + "bytes": 527, + "sha256": "b1ef7047d2b5fa9823b1a2ef05d9638d41750166234718bb60a269fc62f104ac" + }, + { + "path": "fixtures/pages/renotec-geel-jobs.html", + "bytes": 443, + "sha256": "d80bb9841e2cb1a9a05e19e65d02b8190addf2a513c6ed4a7709c088fc35817b" + }, { "path": "fixtures/pages/sample_generic_job.html", "bytes": 502, @@ -1180,6 +1555,56 @@ "bytes": 1454, "sha256": "18f9ad33ec462ea40566eb0e7e6f711fdbb3e732fc85e10be3046e5d0da6ad93" }, + { + "path": "fixtures/pages/sanofi-geel-jobs.html", + "bytes": 571, + "sha256": "f36db79876bd1adc0430fae252000e50f71326fa2c46efbb143958c013191fd0" + }, + { + "path": "fixtures/pages/sck-cen-jobs.html", + "bytes": 337, + "sha256": "5bd6fdd820606b8fa137de294c84fefe80eca49ac923e447ad69b47ede2387ea" + }, + { + "path": "fixtures/pages/stad-geel-jobs.html", + "bytes": 324, + "sha256": "b5caddbf34cfc498c99e6b0c64570c0289584ee408a79b1946ea932ae16a3188" + }, + { + "path": "fixtures/pages/stad-turnhout-jobs.html", + "bytes": 341, + "sha256": "cdf12877fb574452d68b35d3b03a57a05731988457d0183c68a667aa2a59cb00" + }, + { + "path": "fixtures/pages/thomas-more-jobs.html", + "bytes": 620, + "sha256": "344ce3c600a0ff15cb667b03a596313ad09492fc2113b6b9a6128c54997d0972" + }, + { + "path": "fixtures/pages/uhasselt-jobs.html", + "bytes": 425, + "sha256": "5f13da27cda8d4313094762588122d86a13fd40170423ce89d9d944e2b9fe789" + }, + { + "path": "fixtures/pages/vanroey-jobs.html", + "bytes": 538, + "sha256": "a9ef7012fcea0ce1549acc8ec598ec22dbe36f2a5c24cdedc1b82d7709143672" + }, + { + "path": "fixtures/pages/xplore-group-jobs.html", + "bytes": 369, + "sha256": "667c1ae138e14724da840f6b402888ac25ea7d9aba62a2b2376c6a1e8ed9d862" + }, + { + "path": "fixtures/pages/ziekenhuis-geel-jobs.html", + "bytes": 471, + "sha256": "e8785ddbf36254f6f87e8fd803c6768f77d03a79181411330030046148524c56" + }, + { + "path": "fixtures/pages/zol-jobs.html", + "bytes": 381, + "sha256": "a9f657f8e2d91b79fed284bf4506a5abef2ddb862c35314d6290ccfa291443c7" + }, { "path": "LICENSE", "bytes": 1061, @@ -1207,13 +1632,13 @@ }, { "path": "pyproject.toml", - "bytes": 1844, - "sha256": "b0082d3efc0a35429f5eddbbcf2784f83506cf51ddaf4744a64c40da6e6934a1" + "bytes": 1912, + "sha256": "7d4566a38c7603c5d264a03792acb31e55fe1a107a00069eb35c5b4c4c6bdf83" }, { "path": "README.md", - "bytes": 8532, - "sha256": "f8e27889ee485ae2dfc264f39a0c2a35c1f8465574212a9bd9b807e16403d158" + "bytes": 9419, + "sha256": "9cef6efa806e4270945f12051366670060d481264df992554b6d6c576a88fdb9" }, { "path": "scripts/backlog.py", @@ -1232,13 +1657,13 @@ }, { "path": "scripts/codex_bootstrap.sh", - "bytes": 1215, - "sha256": "cd88b92082d011b9c4656f80db8ac974a294ad05ba15cdc881f703c8e25bae13" + "bytes": 1319, + "sha256": "37c1f2e19ec60aea4425301c61dfa5f21d101a4603968780b8097843ab91e302" }, { "path": "scripts/codex_verify.sh", - "bytes": 370, - "sha256": "985b346f381648373369e9c80bdfa854f3b90961311bcefc4fa479e2a81ccc57" + "bytes": 897, + "sha256": "619eccea2c0570fd813105818b30840244d26946fa3ea09a63c1b403efaf57c3" }, { "path": "scripts/deploy_docker.sh", @@ -1277,8 +1702,8 @@ }, { "path": "scripts/release_verify.sh", - "bytes": 12868, - "sha256": "a8139f910e1bea5e7eb415df18edf76539de629adb10e31c5a2ef844594542e6" + "bytes": 12955, + "sha256": "ad54bdaa542f1cdb85ec6e8c867d9621c73dd367d453c4f7593df2e5004d6c82" }, { "path": "scripts/remote_deploy.sh", @@ -1295,10 +1720,15 @@ "bytes": 899, "sha256": "8cfe253e172777f04e0072de08519e35b37969dc98672286b948ad0ae4ec234d" }, + { + "path": "scripts/unraid_aio_entrypoint.sh", + "bytes": 2172, + "sha256": "710bef477af9138dc9713626637fb1fbbb1b6a8592602cabe7bb00bb64de4543" + }, { "path": "scripts/validate_repository.py", - "bytes": 6340, - "sha256": "222ac0ff2edcbc893b364d13fee6954b001c6c6b14c0352ee6227fb04f58c861" + "bytes": 6602, + "sha256": "adcfb8ef285fee7977aefecd295b0de674e42e900f90e9d6712aada5a9786cb4" }, { "path": "scripts/validate_task_ledger.py", @@ -1317,43 +1747,138 @@ }, { "path": "static/css/app.css", - "bytes": 29522, - "sha256": "9343c4b4b22bb64e9dea82bc0598cf7e20b6e4465d4939698c25876f0bc57b4a" + "bytes": 167, + "sha256": "7d9d7af305fe2a432dc063a45b4498a837d8d3d3a02e391a515ef7d41e0013ea" + }, + { + "path": "static/css/auth.css", + "bytes": 10851, + "sha256": "7eece0e2ccc5287a3ce1989ce420d6d756e4cc7fab3a16f20a545837612790e9" + }, + { + "path": "static/css/base.css", + "bytes": 3766, + "sha256": "90eb9e126b7a648477a9b905613e6e952be8d9ebb8f13b867b0223f6f449037e" + }, + { + "path": "static/css/components.css", + "bytes": 8969, + "sha256": "06d20ce96284a9a6467a4490ae4d8cef2289fe068c3fa883e7a17d3bc003bbe3" + }, + { + "path": "static/css/layout.css", + "bytes": 5792, + "sha256": "abc44cfb128364eb85596d41640088d9d22dc3ac1f223297d0d8a8095724f7c3" + }, + { + "path": "static/css/pages.css", + "bytes": 42761, + "sha256": "992ff9b3b369c0acf6094309a703a4c52745f4a1863f1191f9231976e9bdf710" + }, + { + "path": "static/css/responsive.css", + "bytes": 9625, + "sha256": "d7db04f2159866ecf86be8b61104a7ad25e586faf21207308ab0cc04ec032d5d" + }, + { + "path": "static/css/tokens.css", + "bytes": 1675, + "sha256": "37d83df326682b9a77010739a2a0face681ae73678ef30ac6c2dd250ffea4629" + }, + { + "path": "static/favicon.svg", + "bytes": 363, + "sha256": "ff0b841559133c88c7d9a9c804a3b56a80e3cf192c965fd850f3d7995c527b2d" + }, + { + "path": "static/fonts/geist-400.woff2", + "bytes": 33400, + "sha256": "e6ef6abb39c762ed2259cc814e6f16888c540cd84835962227b56ab2354f27f1" + }, + { + "path": "static/fonts/geist-600.woff2", + "bytes": 35292, + "sha256": "9d99fbd791968493fa507ac846de561cee47b00f8100c23cad333b3cb78392d6" + }, + { + "path": "static/fonts/geist-900.woff2", + "bytes": 35816, + "sha256": "0a2bc6d8246d3bb9a23db4ca25dc5f32e9974be3b0dfe7facb45dd68ece2ee8f" + }, + { + "path": "static/fonts/jetbrains-mono-500.woff2", + "bytes": 21832, + "sha256": "cb182feeed4d798ff6961d3c79f7026279448fca0676438aaecb21f3fc39553a" + }, + { + "path": "static/img/vacatureradar-docker.png", + "bytes": 7111, + "sha256": "5037a1ec225af75e91f0f31bb8496d1b627cc9e03fe67423d42a8a290253b894" + }, + { + "path": "static/img/vacatureradar-logo.svg", + "bytes": 424, + "sha256": "382879307fff6e6d0ad03f80723b132f753b0888ab97f5bbad17ef9d567b4f66" }, { "path": "static/js/app.js", - "bytes": 2160, - "sha256": "e601f9fba09bafb6d3783d54212e849a8b2fa0efd0d989237e89a71d70d30cc9" + "bytes": 2828, + "sha256": "ec7d0e0c0517f8f9f7bac9d43faa5a642c94bb982553baec649b9aee5d08b65f" }, { "path": "static/js/manual_import.js", "bytes": 1728, "sha256": "f33a0c2df55fb44da570076172cf1dd54f168b6fe07bee93494f351b2c965f72" }, + { + "path": "stitch_vacatureradar_intelligence_cockpit (1).zip", + "bytes": 206583, + "sha256": "036308b42b1b838d2d30528bd36499fd8a021873618e1e4e12e72d023d034486" + }, + { + "path": "templates/403.html", + "bytes": 526, + "sha256": "e2994ac5bf0fe7c811d91adc50657a472aac826a03c8c1a1d9882f4b9a8dfd89" + }, + { + "path": "templates/404.html", + "bytes": 487, + "sha256": "466c9a160d27599995b8fd0b89b01de1e73c6c68d22a0e0e79e6b7146afcde97" + }, + { + "path": "templates/500.html", + "bytes": 505, + "sha256": "13cbd810427e06b62ca5e0d54dfb08421a86972916867fc8aaaa073594e71813" + }, + { + "path": "templates/activity/list.html", + "bytes": 3273, + "sha256": "3c37a8650abd9a21fb4f98d90f616c5c83a64552f3c35a2a0595aaf523c01b2a" + }, { "path": "templates/applications/edit.html", - "bytes": 1528, - "sha256": "db67de672690f72ae4f46bd7282f007481ca981df5bb83d18b5fdfb83af8eaa1" + "bytes": 2956, + "sha256": "0713c29954833f61c2eaecb7dda8419ff8359eecacc9611ca9b344bc271ccb0a" }, { "path": "templates/applications/list.html", - "bytes": 1949, - "sha256": "2b3a72573223a6847165304dbcdfda0b8b8ed1debc31bcbc7885736e9fcd73d5" + "bytes": 4725, + "sha256": "b729b5dc5411309aa6b43fe97d4ad5f76c1e8fd5aaa9148ea35124c896e1038e" }, { "path": "templates/base.html", - "bytes": 5612, - "sha256": "c7c70b22127b9df20d1746049aa9d44faa2b3ca21a6c540bac2ab7557672aa30" + "bytes": 9029, + "sha256": "2e98cb4c987a248f612a2ca585ce838e7c0e603d557cfd86116e7f1d6713df77" }, { "path": "templates/components/icon_sprite.html", - "bytes": 2853, - "sha256": "1c41232f40b667e0d4a706da5798a1a68f241b39ecc48507366132de0f662b63" + "bytes": 4347, + "sha256": "43a0e05b1d65c16d0f76aa948afa8b834c9f4652f1ef1821a41481e754cecfa9" }, { "path": "templates/dashboard/today.html", - "bytes": 5204, - "sha256": "5f0e78d1d41c1036e890343dec4b967453c1f6c78ce8fa4a022e08d5145afc40" + "bytes": 6907, + "sha256": "7b6d4cd911e9f13dc9e3a28b57432bb8d082a9a0d1a38307b6b75862be5c6a16" }, { "path": "templates/emails/digest.html", @@ -1375,40 +1900,85 @@ "bytes": 682, "sha256": "beb7766d424011d3688eecf4315c4cb23058e07226eb5565478eccb5383f9415" }, + { + "path": "templates/employers/detail.html", + "bytes": 4620, + "sha256": "cba593e77278939634e04e18b2a0fdc50a204eb641fa2595271608a51f83ef47" + }, + { + "path": "templates/employers/list.html", + "bytes": 5740, + "sha256": "2bea0bb4744723a4602c9d2b57ee9c41e37f932c05db174428dcf7599f6a5fb6" + }, { "path": "templates/jobs/detail.html", - "bytes": 6125, - "sha256": "25cff571861995c3498e01293bbd9e656582dd5d2536d8e368a960d6be87bbe0" + "bytes": 10736, + "sha256": "7d7852de438eab0fae6e43f3fa5d5333f27987fd803586e980686b138238f901" }, { "path": "templates/jobs/list.html", - "bytes": 3381, - "sha256": "586891f02d9750ec9a5c41e045a161e4f904383ba1aa5bd1abfe9e24a8b1bce7" + "bytes": 10000, + "sha256": "edbfaf3b5bfd4cd2a9d8aae80780a62f3332d07e125fb08a574fb7c515ee7538" + }, + { + "path": "templates/jobs/skill_insights.html", + "bytes": 7421, + "sha256": "ef9a5ce17bccb5917385a8f9ca16f08fcd903a7fa736479cc8f000d802f7ec47" }, { "path": "templates/profiles/edit.html", - "bytes": 1976, - "sha256": "740ba48670743698e3f3fa833caf3035357159580aecf3cc25caad9f56d7ca32" + "bytes": 8764, + "sha256": "c3f56ddcf09b8801c297d8b2eb65b3f0909e01d32a1693042a4a71ee25250ab1" }, { "path": "templates/profiles/list.html", - "bytes": 1729, - "sha256": "2d14f3e25d4e4795f0b388454982a01294d640954f21783fa62aef1be14b6ec9" + "bytes": 1690, + "sha256": "5015f46bc478384f0bc2b0b2dea76fa604536c99cfa9435886d8f8abb7b236c4" }, { "path": "templates/registration/login.html", - "bytes": 840, - "sha256": "b26bea5cca8afbe255dac4d4b7df6626979b6ae5acfa4a17056640021ae02560" + "bytes": 6243, + "sha256": "040770b92d7ad4a7a359cd4f6ad37cd820ec4c2adf9e7855d5a6520388045d08" + }, + { + "path": "templates/registration/password_reset_complete.html", + "bytes": 1415, + "sha256": "6bc77cefd19850711bf8809944c17801373d37bf96c56cab8f62fcd613c09f7a" + }, + { + "path": "templates/registration/password_reset_confirm.html", + "bytes": 2978, + "sha256": "3ef47c477a5f129265dae53b7810f57cbd4050c9998658d7b2ae268966efa3fe" + }, + { + "path": "templates/registration/password_reset_done.html", + "bytes": 1373, + "sha256": "32efffb516923ef982998fa80a75982bd4be72260fd1738869800e9b5e26cd1d" + }, + { + "path": "templates/registration/password_reset_email.html", + "bytes": 460, + "sha256": "d0372465f9adc5a80434927af172e7fc967f87000bbcec409ca49bec6b5b9527" + }, + { + "path": "templates/registration/password_reset_form.html", + "bytes": 1998, + "sha256": "ea27a14361ae7ded6c7185cc9d05ce197388097c6be38b9ec9a66168356768d4" + }, + { + "path": "templates/registration/password_reset_subject.txt", + "bytes": 40, + "sha256": "4be879a58d088a3643ea3302c6ee0cee4ace2310d073617383dea5b5d5501b7d" }, { "path": "templates/sources/list.html", - "bytes": 8548, - "sha256": "602cba691063a27c42aac69a2dd3b997c627dd4f182f1f9f00aac8561e23306c" + "bytes": 15797, + "sha256": "3e76983bdc8b10ef327c1d694da6d123d89fd5ada1e1c97d5ea5a02e68de54da" }, { "path": "templates/system/status.html", - "bytes": 1528, - "sha256": "57e31c7c1df548043587530601015168b8cf2dff352c8cd04176b4e15f1696e8" + "bytes": 4771, + "sha256": "9f89ecdb19fa9103f9cd8fef71a039cd7a15da0a8b8c68097f3a7cc45c01651e" }, { "path": "tests/__init__.py", @@ -1427,13 +1997,18 @@ }, { "path": "tests/e2e/test_vr114_browser_and_accessibility.py", - "bytes": 11161, - "sha256": "0b1b030cea0699f48ee1d695746eb1c961d16a6e198540a045dbaae9345034fc" + "bytes": 11465, + "sha256": "588d75af1ae2ddd44551d76a38a983543da7831e83282d3045884bea62f56336" }, { "path": "tests/integration/test_applications.py", - "bytes": 7825, - "sha256": "ebbe5a42a7d12203fc4be3ca840495873224853146a999d9b05a35d58a58750b" + "bytes": 9058, + "sha256": "ce980601b6b3b26d3cf939d66c66abdf82e002a64cd9c0d8837f4d12ae2dcf2e" + }, + { + "path": "tests/integration/test_auth_login.py", + "bytes": 5311, + "sha256": "2668e0af951061eb3faadf9d2f26bd304abb10c6c697611b2994c655c6ad319d" }, { "path": "tests/integration/test_dedupe_lifecycle_feedback.py", @@ -1447,28 +2022,53 @@ }, { "path": "tests/integration/test_email_import_notifications.py", - "bytes": 2618, - "sha256": "d8cdd08f444538e2b8ddc927e96b57a020a82a46de45f8fdd58fce7965ad7cb2" + "bytes": 7233, + "sha256": "a9307c979710d361de452cdd7a297f519bf75dd586ebdef049deea58b6818c13" }, { "path": "tests/integration/test_import_geodata.py", "bytes": 3209, "sha256": "9a653f625777bd6748c0dedc52c53354b99190fbc07134934616594a27429d90" }, + { + "path": "tests/integration/test_intelligence_views.py", + "bytes": 2681, + "sha256": "9dd705566bf9d47d1fda2e53cab0b42c04477bc7223deab66ec51c7d9a91e2cb" + }, + { + "path": "tests/integration/test_mailbox_connections.py", + "bytes": 7848, + "sha256": "00c79810063acb25a97e60a7a542a6223cac4a3be94e64c63cdfdb5d1b314508" + }, { "path": "tests/integration/test_pipeline.py", - "bytes": 5179, - "sha256": "c81bf3f37a7fff1bdf35b978e6a82c2c5282e657b29b63edbeb1a328d778a520" + "bytes": 6492, + "sha256": "1d793fa05ed40cc9e9eb980040d62a6b2270524fbfd57d95edfd5d15a1b1a1c4" + }, + { + "path": "tests/integration/test_profile_preferences.py", + "bytes": 4632, + "sha256": "345a495af760fbc522ecb586d7ba2ec696b9d40ceca5910cd41cd4aab34cafe8" }, { "path": "tests/integration/test_reminders.py", "bytes": 4731, "sha256": "a8de719ef79d79b772d5730d37d61544eb7d646a7152d1c605dbe440033fcd3a" }, + { + "path": "tests/integration/test_seed_sources.py", + "bytes": 3575, + "sha256": "7ba4614295ac0caec989f469d5efd80b6915eb127975e26b55936123c267f888" + }, + { + "path": "tests/integration/test_skill_insights.py", + "bytes": 2908, + "sha256": "c40975e8e0e1e5fe01936a272ecc43ae7bc4ee5aa39a1524080b7b44968fe6f2" + }, { "path": "tests/integration/test_source_health.py", - "bytes": 5333, - "sha256": "1a725d2c8a3eebf60cb3c2407b31831d1b11d15c64b95e14d504ddee357167f8" + "bytes": 6020, + "sha256": "2ceca0be922594c413b70b8d9f13c24e716b262be7cfe40b73a1bac4b4206238" }, { "path": "tests/integration/test_sources_manual_import.py", @@ -1477,23 +2077,23 @@ }, { "path": "tests/integration/test_tasks_and_more_views.py", - "bytes": 8460, - "sha256": "223d7d4415c58c339e99f11d9964c0651c9cb2c612ea9b5bdab2feb9b677af60" + "bytes": 10555, + "sha256": "390129a38d545c96d72582cc904e381abfbdc35ab9c634e6ea3855173796bb83" }, { "path": "tests/integration/test_views.py", - "bytes": 1466, - "sha256": "d903608384a3e8426121e67c0319bfe48738f12b3cd8dd58c02c6fe7d4927319" + "bytes": 6145, + "sha256": "9784f5e94cbf4b43dba2d9b76f943dcc3db990b1b6b7e89c257414c45a854ceb" }, { "path": "tests/security/test_policy.py", - "bytes": 3327, - "sha256": "9b2d73a19dc8dffbeda89fe52e988fa4fb572e2599c78f36d7c756e83afaea09" + "bytes": 3607, + "sha256": "f64e348ec35b66a1fa35bce1f8ed4831925cff7e72d015c23f9ed1fcfc7853c8" }, { "path": "tests/security/test_robots.py", - "bytes": 2885, - "sha256": "f86ad2ef7a48d83ccb97f36dafe51a44ef0519ca011996f928ee2d25527689ac" + "bytes": 4916, + "sha256": "683174b528b51bd11795a722a1861a9ca119400d19f9379048bb8c59ec8c7e4f" }, { "path": "tests/security/test_sanitize.py", @@ -1512,8 +2112,8 @@ }, { "path": "tests/unit/test_ats_adapters.py", - "bytes": 5360, - "sha256": "aefc66fba10d9f7ad11308b08cdbbad4fb566522c929d49ed670f5f6bdb0135b" + "bytes": 7302, + "sha256": "ffe1c0940b3ee1370773fbe6d04813cb837613bcd6ad5f32295adf84f01107cd" }, { "path": "tests/unit/test_canonicalize.py", @@ -1522,8 +2122,8 @@ }, { "path": "tests/unit/test_deployment_configuration.py", - "bytes": 1060, - "sha256": "5ba775564059be474cd4ed4454b02f431598f62a6dffe7ba22dcd1a3ded99800" + "bytes": 3099, + "sha256": "183c3773f24e0b3ed69685e18857b065662935611be3fcd2ecc3bb29f0bf33c9" }, { "path": "tests/unit/test_discovery_rss.py", @@ -1532,8 +2132,8 @@ }, { "path": "tests/unit/test_email_adapter.py", - "bytes": 521, - "sha256": "c08836446bc7be70a8ef1dddce4ea083c4bc70b871878d123c0f7b44f793ebd6" + "bytes": 2516, + "sha256": "8c489408c8a170367ad9736ab39e5d2c15dd90f7beb1cf71a2939b4978dba573" }, { "path": "tests/unit/test_employer_resolution.py", @@ -1548,12 +2148,12 @@ { "path": "tests/unit/test_feedback_learning.py", "bytes": 1477, - "sha256": "9edab6468bc8c2d196e6b1c54720891e4f018fc9eb6dddfe441359180c9c9f3c" + "sha256": "964425e6ae83370b6aac2830357ad648d2aca2aacb6ed91ed62bec21b149de7e" }, { "path": "tests/unit/test_fetcher.py", - "bytes": 5319, - "sha256": "ecdbc46a10c6a68cc2802b21e759ddf44ff7f725f968fc974139204586f336dc" + "bytes": 7092, + "sha256": "6ec279e056152138597796edd6dffaee97716dbeeaa67d972dc7d72d88dffc1a" }, { "path": "tests/unit/test_generic_html_adapter.py", @@ -1570,6 +2170,11 @@ "bytes": 583, "sha256": "d1319359e4cf20dd85c0921d36b01b6552dfa2e2763b1c51485e26e339cf4b3a" }, + { + "path": "tests/unit/test_mailbox_credentials.py", + "bytes": 4333, + "sha256": "814a32dfb615f9af7fe571eb2bd821126687fe3381db7f932457a7172e44c339" + }, { "path": "tests/unit/test_manual_import_service.py", "bytes": 3049, @@ -1582,14 +2187,24 @@ }, { "path": "tests/unit/test_profiles.py", - "bytes": 2285, - "sha256": "1368e9cde247289f083924a99b5ef0ca72814c884dc91750cb2d263cc5184475" + "bytes": 2424, + "sha256": "e8f4fca80c35c0787d4ffd36325cccac603186413af959f68d18deaa667d05f7" }, { "path": "tests/unit/test_rate_limit.py", "bytes": 2504, "sha256": "18a8c3d57c031205a270aa753d9cc8f6effb2bf504760723ab94df7e3d36c5ed" }, + { + "path": "tests/unit/test_regional_adapters.py", + "bytes": 8039, + "sha256": "55bba54eaaf11e08bf50ba275bf402be5eb637495494fe5e2359ca58e9d7bdbc" + }, + { + "path": "tests/unit/test_relevance.py", + "bytes": 2587, + "sha256": "e552cf06d3182d6d98dbcd9b5b590cb3c557475b616011ccb3eae093366a0370" + }, { "path": "tests/unit/test_scheduling.py", "bytes": 2846, @@ -1597,23 +2212,33 @@ }, { "path": "tests/unit/test_scoring.py", - "bytes": 7665, - "sha256": "84d5d0f4a5eaf486f6af0692b93c4e0998067ff07e032f01a091f7e8a003078f" + "bytes": 11375, + "sha256": "81fe128998ca6239fd16842c7184188c7f4f3cae9b82b909629a4eb84ae99e18" }, { "path": "tests/unit/test_settings_security.py", - "bytes": 2050, - "sha256": "9a1e3eecf3d6d51b9ecfdef13f2f063d73428c309e75d92591fb948b9a1b6ccc" + "bytes": 2637, + "sha256": "fbac6846e3fd64d22cab365f8651deb0955866194e45f46593f42e4b14ce108d" + }, + { + "path": "tests/unit/test_skill_demand.py", + "bytes": 2640, + "sha256": "de2a08e068bc4265b908767b0a10f88203635951236a76887cab16649fb09da2" }, { "path": "tests/unit/test_source_models.py", - "bytes": 1389, - "sha256": "ae9d03665f72347ecd313112e5330f4c7d4fd38f650fc833be82a855f6bd5ee1" + "bytes": 2194, + "sha256": "1c6b8f7daaa3e38607fbed01bb50b3838e93257d8c355288bfc5299800a51aef" + }, + { + "path": "tests/unit/test_tls.py", + "bytes": 405, + "sha256": "ee2c11bad8a983474114b2806c199c8e51c4cf3dfb93d76dbede3daad8f0e699" }, { "path": "uv.lock", - "bytes": 87544, - "sha256": "c7931fe4292624e9ab85dbb21ec18ef7c7c5f4b96d13d6f5a9221d0fb0398812" + "bytes": 106081, + "sha256": "3b846377252994715529090c6b20bec6950ca7dbc92e93cbcc277175bcb4d1d5" }, { "path": "VERIFICATION_REPORT.md", diff --git a/docker-compose.unraid.yml b/docker-compose.unraid.yml index b899ba7..e0c41d9 100644 --- a/docker-compose.unraid.yml +++ b/docker-compose.unraid.yml @@ -27,7 +27,8 @@ services: labels: net.unraid.docker.managed: dockerman net.unraid.docker.webui: http://[IP]:[PORT:8080]/ - net.unraid.docker.icon: http://[IP]:[PORT:8080]/static/favicon.svg + # Dockerman downloadt iconen server-side zonder [IP]/[PORT]-substitutie. + net.unraid.docker.icon: http://127.0.0.1:1226/static/img/vacatureradar-docker.png org.opencontainers.image.title: VacatureRadar org.opencontainers.image.description: Persoonlijke vacature-intelligence voor de Belgische arbeidsmarkt healthcheck: diff --git a/docs/ai/BACKLOG.yaml b/docs/ai/BACKLOG.yaml index d60f26e..5820c8d 100644 --- a/docs/ai/BACKLOG.yaml +++ b/docs/ai/BACKLOG.yaml @@ -1,6 +1,6 @@ schema_version: 1 project: VacatureRadar -updated_at: '2026-07-22' +updated_at: '2026-07-26' execution_policy: order: file-order, then first ready task whose dependencies are done no_product_questions: true @@ -1748,6 +1748,138 @@ tasks: completed_at: '2026-07-22' note: Alle negen Stitch-schermen zijn browsergevalideerd van 360 tot 2560 px; elf screenshots, accessibility-/legacy-/queryaudit en documentatie afgerond. Volledige gate groen met 248 tests, 2 optionele skips en 84,40% coverage. +- id: VR-214 + title: Herstel het Unraid Dockerman-containericoon + status: done + priority: P1 + requirement_ids: + - NFR-009 + depends_on: + - VR-213 + summary: Vervang de fragiele runtime-SVG voor de Dockerman-tegel door een lokale rasterasset en dek het volledige + Unraid-label- en packagingcontract af. + acceptance_criteria: + - Dockerman verwijst via de gepubliceerde webpoort naar een lokale PNG die door collectstatic in de image wordt + opgenomen. + - De browserfavicon blijft lokaal en ongewijzigd beschikbaar. + - Een regressietest controleert de exacte WebUI- en icoonlabels, PNG-signatuur en verwachte afmetingen. + - Het Unraid-runbook beschrijft het cachegedrag en de veilige refreshstap. + verification: + - uv run pytest tests/unit/test_deployment_configuration.py tests/integration/test_views.py + - ./scripts/codex_verify.sh + primary_paths: + - docker-compose.unraid.yml + - static/img/vacatureradar-docker.png + - tests/unit/test_deployment_configuration.py + - docs/operations/UNRAID_DEPLOYMENT.md + result: + completed_at: '2026-07-25' + note: Dockerman-icoon vervangen door lokale 256x256 PNG via exact Unraid-poortlabel; URL, PNG-signatuur en afmetingen + regressiegedekt. Volledige gate groen met 264 tests en 83,47% dekking. Live Unraid-image sha256:799825df1a343b6bee43695850454a5fdc977b5af3366077e6ac6aaeeb94f6f9 + gebouwd en gerecreate; container healthy, readiness/liveness groen en PNG via LAN HTTP 200 (7111 bytes). +- id: VR-215 + title: Corrigeer Dockermans server-side icoondownload + status: done + priority: P1 + requirement_ids: + - NFR-009 + depends_on: + - VR-214 + summary: Gebruik voor het Dockerman-icoon een concrete server-side URL omdat Unraid placeholders alleen voor WebUI-links + vervangt en niet voor icoondownloads. + acceptance_criteria: + - Het icoonlabel bevat geen IP- of poortplaceholder en is vanaf de Unraid-host bereikbaar. + - Dockerman maakt `VacatureRadar-icon.png` in zijn eigen icooncache aan. + - De Docker-tegel gebruikt de gecachte PNG terwijl WebUI-poortsubstitutie behouden blijft. + - De regressietest en het Unraid-runbook leggen dit onderscheid vast. + verification: + - uv run pytest tests/unit/test_deployment_configuration.py + - Live Unraid-recreate, cachecontrole en HTTP-smoke + - ./scripts/codex_verify.sh + primary_paths: + - docker-compose.unraid.yml + - tests/unit/test_deployment_configuration.py + - docs/operations/UNRAID_DEPLOYMENT.md + result: + completed_at: '2026-07-25' + note: Dockerman gebruikt nu concrete server-side loopback-URL zonder placeholders; live Unraid-container healthy, + HTTP-smoke groen, persistente en RAM-cache bevatten geldige 256x256 PNG en docker.json verwijst naar VacatureRadar-icon.png. + Gericht 5 tests en volledige gate groen met 264 tests en 83,44% dekking. +- id: VR-216 + title: Premium UX-verfijning met rustige visuele hiërarchie + status: done + priority: P1 + requirement_ids: + - NFR-004 + - NFR-008 + - NFR-009 + depends_on: + - VR-215 + summary: Verfijn de bestaande Stitch-interface naar een premium, rustige cockpit met minder visuele ruis, selectiever + kleurgebruik en sterkere informatiehiërarchie zonder server-rendered flows te wijzigen. + acceptance_criteria: + - Donker en licht thema gebruiken gelaagde oppervlakken, zachtere scheidingen en cyan alleen voor primaire signalen + en acties; navigatie en labels gebruiken leesbare humane typografie. + - Dashboard, verkenner, detail, pipeline en profiel hebben een herkenbare eigen compositie met consistente componenten, + zonder nepdata of dode bediening. + - Mobiele KPI's staan compact boven de vouw, filters blijven bruikbaar en de pipeline scrolt intern zonder pagina-overflow + tussen 360 en 1440 px. + - Browsercontrole op 390 en 1280 px dekt donker en licht thema, kernflows en een schone console. + verification: + - uv run pytest tests/e2e/test_vr114_browser_and_accessibility.py tests/integration/test_views.py + - ./scripts/codex_verify.sh + - Browsermatrix 390x844 en 1280x800 in donker en licht thema + primary_paths: + - static/css + - templates/dashboard/today.html + - templates/jobs/list.html + - templates/applications/list.html + - templates/profiles/list.html + - docs/design/STITCH_INTEGRATION_REPORT.md + result: + completed_at: '2026-07-25' + note: Premium calm-intelligence-laag voor dark/light, compacte mobiele KPI's, responsieve filtercommandobar, + intern scrollende pipeline en breed profiel. Bestaande login-CSP-fout opgelost met lokale externe JS. Browsermatrix + 390/1280 zonder overflow en schone console; volledige gate groen met 264 tests, 2 optionele skips en 83,44% + dekking. +- id: VR-217 + title: Borg Unraid-authenticatie en voeg interactieve radarillustraties toe + status: done + priority: P1 + requirement_ids: + - NFR-008 + - NFR-009 + depends_on: + - VR-216 + summary: Maak het bestaande SSH-deploymentprofiel expliciet en voeg een lokale, toegankelijke interactieve radarillustratie + toe op inhoudelijk passende cockpitstates zonder echte data of kernflows te vervangen. + acceptance_criteria: + - Deploymentdocumentatie en helper valideren de geconfigureerde Unraid-hostalias en falen met een concrete herstelmelding + wanneer de verkeerde SSH-identiteit wordt gekozen. + - Een herbruikbare lokale radarillustratie versterkt profiel- en lege radarstates met pointer-/focusrespons en + bevat geen externe asset, fictieve metric of betekenisdragende informatie. + - De illustratie degradeert zonder JavaScript, respecteert prefers-reduced-motion en blijft verborgen voor hulptechnologie; + kernacties en tekst blijven zelfstandig bruikbaar. + - Browsercontrole op 390 en 1280 px in donker en licht thema toont geen overflow, consolefout of concurrerende + visuele hiërarchie; de live Unraid-container is na deployment healthy. + verification: + - uv run pytest tests/unit/test_deployment_configuration.py tests/e2e/test_vr114_browser_and_accessibility.py + - Browsermatrix 390x844 en 1280x800 in donker en licht thema + - Live SSH-, containerhealth- en HTTP-smoke via de geconfigureerde hostalias + - ./scripts/codex_verify.sh + primary_paths: + - scripts/deploy_unraid.ps1 + - docs/operations/UNRAID_DEPLOYMENT.md + - templates/components/radar_illustration.html + - templates/profiles/list.html + - static/css/premium.css + - static/js/app.js + result: + completed_at: '2026-07-26' + note: Unraid-authenticatie hersteld via gevalideerde hostalias unraid-itworx met root en dedicated sleutel; + fail-fast deployhelper toegevoegd. Interactieve lokale SVG-radar met pointerparallax, dark/light en reduced-motionfallback + toegevoegd. Browsermatrix 390/1280 zonder overflow of consolefouten; 265 tests groen. Live image sha256:4d30e01e778777d5a2cde4339a9b2ad006edb25cae387293737420152d65069d + healthy en readiness/CSS/JS HTTP 200. - id: VR-301 title: Multi-tenant SaaS, teams en billing status: deferred diff --git a/docs/ai/PROJECT_STATE.md b/docs/ai/PROJECT_STATE.md index 0ca359c..71c84b6 100644 --- a/docs/ai/PROJECT_STATE.md +++ b/docs/ai/PROJECT_STATE.md @@ -1,6 +1,6 @@ # Projectstatus -- Laatst bijgewerkt: 2026-07-25 +- Laatst bijgewerkt: 2026-07-26 - Repositoryversie: 0.3.0 volledige Stitch Intelligence Cockpit - Uitvoeringsmodus: autonome backlog - Eerste uitvoerbare taak: controleer met `python scripts/backlog.py next` @@ -32,6 +32,58 @@ VR-209 tot en met VR-213 vervangen de oude browserfrontend integraal: Het volledige scherm- en afwijkingsrapport staat in `docs/design/STITCH_INTEGRATION_REPORT.md`. +## Laatste onderhoudsaudit + +VR-217 op 2026-07-26: + +- de vermeende Unraid-authenticatiefout bleek een clientprofielkeuze: rechtstreeks IP gebruikte gebruiker `jens`, terwijl `unraid-itworx` correct `root`, `IdentitiesOnly` en de afzonderlijke deploysleutel selecteert; +- `scripts/deploy_unraid.sh` valideert de hostalias, synchroniseert uitsluitend niet-genegeerde projectbestanden en combineert build, containerhealth en readiness-smoke; +- het zoekprofiel heeft een lokale interactieve SVG-radar met scansweep, signalen, pointerparallax en statische reduced-motionvariant zonder fictieve data; +- browsercontrole op 1280×800 en 390×844 in licht en donker toont geen documentoverflow of consolefouten; de illustratie blijft decoratief en staat mobiel na de primaire actie; +- volledige gate groen met 265 tests, 2 optionele skips en 83,44% branch-aware dekking; +- live image `sha256:4d30e01e778777d5a2cde4339a9b2ad006edb25cae387293737420152d65069d` gebouwd en via `unraid-itworx` uitgerold; container healthy, readiness HTTP 200 en de nieuwe CSS-/JS-assets via LAN HTTP 200; +- volgende uitvoerbare taak: geen; alleen bestaande externe blokkades en uitgestelde productkeuzes resteren. + +VR-216 op 2026-07-26: + +- premium “calm intelligence”-laag toegevoegd met zachtere scheidingen, spaarzamer cyan, humane navigatietypografie en duidelijkere elevatie in donker en licht thema; +- dashboard-KPI's staan mobiel compact 2×2, filters vormen een responsieve commandobar, pipelinekolommen scrollen intern en het profiel gebruikt een brede compositie; +- de browsermatrix controleerde 1280×800 en 390×844 zonder documentoverflow; dashboard, verkenner, pipeline, profiel en beide thema's zijn visueel beoordeeld; +- een bestaande CSP-consolefout op login is opgelost door de interactie naar een lokale externe JavaScriptasset te verplaatsen en regressiegedekt; +- gerichte verificatie: Ruff groen, 22 tests geslaagd en 2 optionele Playwright-skips; volledige gate groen met 264 tests, 2 optionele skips en 83,44% branch-aware dekking; +- volgende uitvoerbare taak: geen; alleen bestaande externe blokkades en uitgestelde productkeuzes resteren. + +VR-215 op 2026-07-26: + +- Dockermans eigen implementatie bleek `[IP]`/`[PORT]` uitsluitend voor WebUI-links + te vervangen en niet vóór een icoondownload; het eerdere label kon daardoor de + cache niet vullen; +- het live icoonlabel gebruikt nu de server-side loopback-URL op hostpoort 1226; +- de Unraid-host bevat zowel de persistente als actuele RAM-cache + `VacatureRadar-icon.png`, en Dockermans gegenereerde `docker.json` verwijst naar + `/state/plugins/dynamix.docker.manager/images/VacatureRadar-icon.png`; +- de gerecreate container is healthy en de PNG-/readiness-smokes slagen; +- volgende uitvoerbare taak: geen; alleen bestaande externe blokkades en + uitgestelde productkeuzes resteren. + +VR-214 op 2026-07-25: + +- het ontbrekende Unraid/Dockerman-tegelicoon is hersteld met een lokale 256×256 PNG op + `/static/img/vacatureradar-docker.png`; de browserfavicon blijft de bestaande SVG; +- de volledige WebUI- en icoonlabels, PNG-signatuur en afmetingen zijn regressiegedekt; +- `collectstatic` kopieert de asset naar de productie-staticroot; +- de volledige `scripts/codex_verify.sh`-gate slaagde via Git Bash met 264 tests, + 2 optionele Playwright-skips en 83,47% branch-aware codedekking; +- de Unraid-image is daarna live gebouwd als + `sha256:799825df1a343b6bee43695850454a5fdc977b5af3366077e6ac6aaeeb94f6f9`; + de gerecreate container was healthy, liveness/readiness slaagden en het + Dockerman-label wees naar de lokale PNG, die via LAN HTTP 200 met + `Content-Type: image/png` en 7.111 bytes leverde; +- er zijn tijdens de volledige lint-, systeem-, migratie-, test-, ledger- en + repositorycontrole geen andere releaseblokkerende regressies gevonden; +- volgende uitvoerbare taak: geen; alleen de bestaande externe blokkades en + uitgestelde productkeuzes resteren. + VR-209–213-verificatie op 2026-07-22: - 248 tests geslaagd, 2 optionele Playwrightvarianten overgeslagen en 84,40% branch-aware codedekking; diff --git a/docs/design/STITCH_INTEGRATION_REPORT.md b/docs/design/STITCH_INTEGRATION_REPORT.md index 3bd2d4c..da8c938 100644 --- a/docs/design/STITCH_INTEGRATION_REPORT.md +++ b/docs/design/STITCH_INTEGRATION_REPORT.md @@ -92,3 +92,15 @@ Er is geen oude shell, alternatieve feature flag of parallelle frontendroute. Ou De volledige `scripts/codex_verify.sh`-gate is zowel vóór als na ledgerafronding groen: Ruff, Django system check, `makemigrations --check --dry-run`, 248 geslaagde tests, 2 optioneel overgeslagen Playwrightvarianten, 84,40% branch-aware coverage, backlogvalidatie en repository-/documentvalidatie. Bekende externe beperkingen wijzigen de frontend niet: live mailbox-/SMTP-credentials en productie-infrastructuur blijven afzonderlijke `blocked-external` taken. Niet-bestaande backenddata wordt bewust als onbekend getoond. + +## Premium verfijning (VR-216) + +De high-fidelity cockpit is na een senior UX-audit verfijnd volgens het principe “calm intelligence”. De onderliggende Stitch-informatiearchitectuur en alle server-rendered flows blijven intact; de visuele laag vermindert gelijkwaardige kaders en reserveert cyan voor primaire acties, actieve navigatie en betekenisvolle signalen. Navigatie, labels en hulptekst gebruiken humane typografie, terwijl cijfers en technische statusinformatie hun compacte technische karakter behouden. + +Dashboard-KPI's staan mobiel in een compact 2×2-raster zodat de eerste matches binnen de eerste viewport beginnen. De vacaturefilters gedragen zich als één responsieve commandobar, pipelinekolommen hebben een stabiele interne scroll en scroll-snap, en het zoekprofiel gebruikt een brede samengestelde kaart in plaats van een klein eiland in lege ruimte. Het lichte thema heeft meer tonale diepte en minder grijze vlakheid. + +De geïntegreerde browsercontrole dekte dashboard, verkenner, pipeline en profiel op 1280×800, dashboard en verkenner op 390×844, en het dashboard in donker en licht thema. De gemeten documentbreedte bleef binnen de viewport; op 390 px gebruikte het dashboard twee KPI-kolommen en begon de matchsectie op 550 px. De loginconsole is schoon nadat de voorheen inline logininteractie naar `static/js/login.js` is verplaatst conform de bestaande CSP. + +### Interactieve illustratielaag + +Het zoekprofiel gebruikt een herbruikbare lokale SVG-radar als projectillustratie. Scan, signalen en orbitale details bewegen langzaam; pointerbeweging geeft maximaal tien pixels parallax. De visual is `aria-hidden`, bevat geen metric of statusclaim en laat alle profielinformatie en acties zelfstandig intact. Zonder JavaScript blijft de compositie bruikbaar en `prefers-reduced-motion: reduce` verwijdert animatie en parallax volledig. Op 390 px staat de illustratie onder de primaire profielactie, zodat zij de kernflow niet uit de eerste leesvolgorde verdringt. diff --git a/docs/operations/UNRAID_DEPLOYMENT.md b/docs/operations/UNRAID_DEPLOYMENT.md index 45c9fbd..43f6814 100644 --- a/docs/operations/UNRAID_DEPLOYMENT.md +++ b/docs/operations/UNRAID_DEPLOYMENT.md @@ -23,9 +23,17 @@ Gebruik bij voorkeur een cache-backed `appdata`-share en neem die afzonderlijk i De container verschijnt als **VacatureRadar** en bevat metadata voor: - WebUI: `http://[IP]:1226/`; -- icoon: het lokale VacatureRadar-favicon op `/static/favicon.svg`; +- icoon: het lokale 256×256 VacatureRadar-PNG op `/static/img/vacatureradar-docker.png`; - één herstart- en healthcheckpunt voor alle interne processen. +Dockerman haalt het tegelicoon via de gepubliceerde webpoort op en cachet het. De +PNG is daarom een afzonderlijke rasterasset met een stabiel pad; de SVG-favicon +blijft voor browsers behouden. Het icoonlabel gebruikt bewust de concrete +server-side URL `http://127.0.0.1:1226/...`: Dockerman vervangt `[IP]` en +`[PORT:...]` alleen voor de WebUI, niet voordat het een icoon downloadt. Na een +upgrade waarbij het oude icoon nog zichtbaar is, herlaad de Docker-pagina zodat +Dockerman de lokale cache opbouwt. + ## Imagekeuze De Unraid-compose bouwt lokaal met `Dockerfile.unraid` en tagt de image als `vacatureradar:unraid`. Pin voor reproduceerbare releases aanvullend een commitgebonden tag. @@ -81,6 +89,28 @@ python manage.py collectstatic --noinput 7. Meld lokaal aan en wijzig het bootstrapwachtwoord. 8. Activeer nog geen live bron of mailbox voordat bronbeleid, retentie en back-up zijn gecontroleerd. +## Beheerverbinding en heruitrol + +Gebruik vanaf een beheerwerkstation altijd een benoemde SSH-hostalias met een afzonderlijke deploysleutel. Een rechtstreekse verbinding naar het IP-adres kan de lokale standaardgebruiker en standaardsleutel kiezen en daardoor ten onrechte als een server-side authenticatiefout lijken. + +```sshconfig +Host unraid-itworx + HostName 192.168.10.150 + Port 22 + User root + IdentityFile ~/.ssh/itworx_unraid_deploy + IdentitiesOnly yes +``` + +Controleer de verbinding zonder wachtwoordprompt en voer daarna de beveiligde synchronisatie, imagebuild, containervervanging en health-smoke uit: + +```bash +ssh -o BatchMode=yes unraid-itworx 'id -un && docker info >/dev/null' +bash scripts/deploy_unraid.sh +``` + +`deploy_unraid.sh` weigert een IP-adres, niet-rootprofiel of generieke standaardsleutel. Alleen door Git gekende of niet-genegeerde projectbestanden worden verpakt; `.env`, `local/`, databasebestanden en andere genegeerde runtimegegevens worden niet verzonden. De helper wacht op Docker-health en controleert `/health/ready/` voordat hij succes meldt. Een afwijkende veilige alias kan expliciet via `UNRAID_SSH_HOST` worden ingesteld. + ## Releaseverificatie en upgradepad VR-117 voegt `./scripts/release_verify.sh` toe als release-check met artifacts voor package, checksum, configdiff, sbom en upgrade/rollback. diff --git a/docs/quality/THREAT_MODEL.md b/docs/quality/THREAT_MODEL.md index 43cf81a..436d758 100644 --- a/docs/quality/THREAT_MODEL.md +++ b/docs/quality/THREAT_MODEL.md @@ -48,7 +48,7 @@ Django/Celery |---|---|---|---|---| | T-01 | SSRF via bron- of vacature-URL | toegang tot LAN, metadata of beheerinterfaces | schema/host/IP/poortcontrole, DNS-resolutie, redirecthercontrole, denylist, geen credentials in URL | `tests/security/test_url_security.py`, `test_policy.py`, fetchertests | | T-02 | DNS rebinding/tijd-van-controle | request bereikt ander IP dan beoordeeld | iedere fetch en redirect valideert DNS opnieuw; het werkelijk verbonden peer-IP wordt als publiek gecontroleerd wanneer de transportlaag dit exposeert, anders geldt conservatieve DNS-set-overlap | `VR-115`, `tests/unit/test_fetcher.py`, aanvullende fetcher negative tests | -| T-03 | XSS/HTML-injectie | sessiediefstal of ongewenste navigatie | Bleach allowlist, platte tekst, template autoescaping, CSP/securityheaders | `tests/security/test_sanitize.py`, viewtests | +| T-03 | XSS/HTML-injectie | sessiediefstal of ongewenste navigatie | Bleach allowlist, platte tekst, template autoescaping, CSP/securityheaders en uitsluitend lokale externe scripts zonder `unsafe-inline` | `tests/security/test_sanitize.py`, auth- en viewtests | | T-04 | Prompt injection in vacature | AI verandert beleid of voert tools uit | vacaturetekst in data-envelope, schema-output, evidence-validatie, cachefouten met foutcategorie, geen toolrechten, deterministische harde regels, AI optioneel | AI-tests, ADR-0003, `VR-109` evaluaties | | T-05 | Malafide/oversized document | geheugen/CPU/diskuitputting | contenttype-, byte-, timeout- en redirectlimieten; korte retentie | fetchertests, source-runmetrics | | T-06 | Crawlstorm of ban | externe overlast, IP-blokkade | expliciet allowbeleid, per-origin minimuminterval, concurrencylimiet, leasegebaseerde taakclaim met TTL + crash recovery, conditional requests, backoff, quarantaine | tasktests; `VR-103`, `VR-112` | diff --git a/docs/quality/TRACEABILITY_MATRIX.md b/docs/quality/TRACEABILITY_MATRIX.md index f806b9e..49d174d 100644 --- a/docs/quality/TRACEABILITY_MATRIX.md +++ b/docs/quality/TRACEABILITY_MATRIX.md @@ -63,8 +63,8 @@ Legenda: **I** = geïmplementeerd en getest in de huidige MVP; **P** = gedeeltel | NFR-005 | I | domeinapps/services/adapters/ADRs | continue review | | NFR-006 | I | offline pytest + coveragegate | VR-114 verbreedt e2e | | NFR-007 | P | `scripts/benchmark.py`, `tests/benchmark/test_benchmark.py` | VR-116 | -| NFR-008 | I | volledige negen-schermen-Stitch-cockpit, acht viewports, dark/light, toetsenborddrawer en elf actuele screenshots | `tests/e2e/test_vr114_browser_and_accessibility.py`, `tests/integration/test_intelligence_views.py`, `artifacts/visual-validation/`, VR-209–213 | -| NFR-009 | I | lokale SQLite en Docker/Unraidconfig, release-smoke en restore | VR-117 afgerond | +| NFR-008 | I | volledige negen-schermen-Stitch-cockpit, acht viewports, dark/light, toetsenborddrawer, premium visuele hiërarchie en toegankelijke interactieve radarillustratie | `tests/e2e/test_vr114_browser_and_accessibility.py`, `tests/integration/test_intelligence_views.py`, `artifacts/visual-validation/`, VR-209–213, VR-216/217 | +| NFR-009 | I | lokale SQLite, Docker/Unraidconfig, gevalideerde SSH-deployalias, release-smoke en restore | VR-117 en VR-217 afgerond | | NFR-010 | I | `apps/sources/services/health.py`, `apps/sources/tasks.py`, `apps/core/views.py`, `config/settings.py` | `tests/integration/test_source_health.py` | VR-112 observability | ## Systeemacceptatie @@ -102,3 +102,7 @@ Iedere backlogtaak die een requirement wijzigt, werkt in dezelfde commit deze ma - VR-207 breidt het profiel uit met gegroepeerde field-/infrastructuurrollen, cv-skills en een ervaringsband. Skill-evidence is op vier treffers begrensd, afwezigheid wordt niet als tekort gepresenteerd en ambigue fieldtitels vereisen een onafhankelijk IT-contextsignaal. Persoonlijke cv-waarden staan uitsluitend in de lokale gebruikersdatabase; repositorytests en documentatie blijven gesaneerd. - VR-208 maakt die ervaringsband uitsluitend informatief en verwijdert ook senioriteitsinvloed uit de AI-component en feedbacklearning. De gebruikersgescopeerde Skillsradar analyseert actuele niet-uitgesloten IT-vacatures van actieve bronnen, onderscheidt skillveldbewijs van tekstinferentie en toont profieldekking en mogelijke leerkansen met categorie-/dekkingsfilters. - VR-209–213 vervangen de volledige browserfrontend door de negen Stitch Intelligence Cockpit-composities. De nieuwe shell, dashboard/vacature-intelligence, pipeline/profiel/bronnen, automation/activiteit/werkgevers en responsive validatie zijn gekoppeld aan echte read models en serveracties. Bewijs staat in `docs/design/STITCH_INTEGRATION_REPORT.md`, route-/scope-/querytests en de elf beelden onder `artifacts/visual-validation/`. +- VR-214 herstelt het ontbrekende Dockerman-tegelicoon met een lokale 256×256 PNG, een exact Unraid-poortlabel en een regressietest die URL, PNG-signatuur en afmetingen controleert. +- VR-215 corrigeert het Dockerman-downloadcontract: het icoonlabel gebruikt een concrete loopback-URL omdat Dockerman placeholders alleen voor WebUI resolveert; live bewijs omvat de aangemaakte `VacatureRadar-icon.png`-cache. +- VR-216 verfijnt de Stitch-cockpit naar “calm intelligence”: minder visuele ruis, selectief cyan, compactere mobiele KPI's, responsieve filters, intern scrollende pipeline en een CSP-veilige logininteractie. +- VR-217 borgt de werkende Unraid-hostalias en afzonderlijke deploysleutel in een fail-fast releasehelper en voegt een lokale decoratieve radarillustratie met pointerrespons en reduced-motionfallback toe. diff --git a/scripts/deploy_unraid.sh b/scripts/deploy_unraid.sh new file mode 100644 index 0000000..89f76af --- /dev/null +++ b/scripts/deploy_unraid.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +unraid_host="${UNRAID_SSH_HOST:-unraid-itworx}" +remote_root="${UNRAID_APP_ROOT:-/mnt/user/appdata/vacatureradar/source}" +archive_name="vacatureradar-deploy-$$.tar.gz" +local_archive="$(mktemp -t "${archive_name}.XXXXXX")" +trap 'rm -f "$local_archive"' EXIT + +ssh_config="$(ssh -G "$unraid_host" 2>/dev/null)" +ssh_user="$(printf '%s\n' "$ssh_config" | awk '$1 == "user" { print $2; exit }')" +identity_file="$(printf '%s\n' "$ssh_config" | awk '$1 == "identityfile" { print $2; exit }')" + +if [[ "$unraid_host" == *.* ]] || [[ "$ssh_user" != "root" ]] || [[ "$identity_file" == *"id_ed25519" ]]; then + echo "Onveilig of onvolledig Unraid-SSH-profiel: '$unraid_host'." >&2 + echo "Gebruik een Host-alias met User root, IdentitiesOnly yes en een afzonderlijke deploysleutel." >&2 + echo "Verwacht lokaal standaard: Host unraid-itworx / IdentityFile ~/.ssh/itworx_unraid_deploy" >&2 + exit 2 +fi + +ssh -o BatchMode=yes -o ConnectTimeout=8 "$unraid_host" "test \"\$(id -un)\" = root && docker info >/dev/null" + +git ls-files --cached --others --exclude-standard -z \ + | tar --null --files-from=- --create --gzip --file="$local_archive" + +scp -q "$local_archive" "$unraid_host:/tmp/$archive_name" +ssh "$unraid_host" "set -eu + mkdir -p '$remote_root' + tar -xzf '/tmp/$archive_name' -C '$remote_root' + rm -f '/tmp/$archive_name' + cd '$remote_root' + docker compose -f docker-compose.unraid.yml up -d --build + timeout 120 sh -c 'until docker inspect VacatureRadar --format=\"{{.State.Health.Status}}\" 2>/dev/null | grep -qx healthy; do sleep 3; done' + curl --fail --silent --show-error http://127.0.0.1:1226/health/ready/ >/dev/null + docker ps --filter name=VacatureRadar --format '{{.Names}} {{.Status}} {{.Image}}'" diff --git a/static/css/app.css b/static/css/app.css index f577e06..ad98855 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -4,3 +4,4 @@ @import url("components.css"); @import url("pages.css"); @import url("responsive.css"); +@import url("premium.css"); diff --git a/static/css/premium.css b/static/css/premium.css new file mode 100644 index 0000000..465e028 --- /dev/null +++ b/static/css/premium.css @@ -0,0 +1,206 @@ +/* VR-216: premium refinement layer — calm hierarchy over decorative chrome. */ +:root { + --surface-raised: color-mix(in srgb, var(--surface-container) 96%, white); + --divider: color-mix(in srgb, var(--outline) 29%, transparent); + --divider-strong: color-mix(in srgb, var(--outline) 46%, transparent); + --shadow-card: 0 18px 48px rgba(0, 0, 0, .13); + --radius-sm: 7px; + --radius-md: 12px; + --radius-lg: 18px; +} + +:root[data-theme="light"] { + --surface-lowest: #e9f0f1; + --surface: #f4f7f7; + --surface-low: #edf2f2; + --surface-container: #fbfdfd; + --surface-high: #e3ebeb; + --surface-highest: #d6e2e2; + --outline: #aebfc0; + --shadow-card: 0 18px 45px rgba(31, 54, 57, .08); +} + +body::before { + background: + radial-gradient(circle at 82% -8%, color-mix(in srgb, var(--cyan) 7%, transparent), transparent 33rem), + radial-gradient(circle at 24% 108%, color-mix(in srgb, var(--indigo) 4%, transparent), transparent 38rem); +} + +.cockpit-sidebar, +.cockpit-topbar, +.sidebar-heading, +.sidebar-account, +.mobile-tabbar { border-color: var(--divider); } + +.cockpit-sidebar { background: color-mix(in srgb, var(--surface-lowest) 97%, black); } +.cockpit-main { padding-top: calc(var(--topbar-height) + 42px); } +.cockpit-main > * { max-width: 1860px; } + +.nav-caption, +.nav-item, +.mobile-tabbar a, +.sidebar-account small { font-family: var(--font-body); } +.nav-caption { font-weight: 700; letter-spacing: .11em; } +.nav-item { min-height: 46px; border-radius: 9px; font-size: 14px; font-weight: 560; letter-spacing: -.005em; } +.nav-item:hover { transform: none; background: color-mix(in srgb, var(--surface-high) 70%, transparent); } +.nav-item.is-active { background: var(--surface-high); box-shadow: inset 0 0 0 1px var(--divider); } +.nav-item.is-active::after { inset: 9px auto 9px 0; width: 3px; border-radius: 0 3px 3px 0; box-shadow: none; } + +.cockpit-heading { margin-bottom: 30px; } +.cockpit-heading h1 { max-width: 900px; } +.cockpit-heading > div:first-child > p:last-child { font-size: 15px; line-height: 1.65; } +.eyebrow, +.section-kicker, +.kpi-label { color: var(--text-faint); font-family: var(--font-body); font-weight: 700; letter-spacing: .105em; } +.section-kicker { margin-bottom: 7px; } + +.button { min-height: 44px; border-radius: 9px; } +.button-primary { box-shadow: 0 8px 24px color-mix(in srgb, var(--cyan) 13%, transparent); } +.button-secondary, +.button-ghost { border-color: var(--divider-strong); } +.button-ghost { background: color-mix(in srgb, var(--surface-high) 78%, transparent); } +.icon-button { border-radius: 9px; } + +input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), +select, +textarea, +.command-search { border-color: var(--divider-strong); border-radius: 9px; background: color-mix(in srgb, var(--surface-low) 90%, transparent); } +.field-label, +label > span:first-child { font-family: var(--font-body); font-weight: 700; letter-spacing: .065em; } + +.panel, +.tonal-panel, +.job-card, +.result-card, +.source-card, +.health-card, +.profile-card, +.form-panel { + border-color: var(--divider); + background: var(--surface-raised); + box-shadow: 0 1px 0 color-mix(in srgb, white 2%, transparent); +} +.strong-match { border-color: var(--divider); box-shadow: inset 3px 0 var(--cyan-dim), var(--shadow-card); } +.panel-subtle { background: var(--surface-low); box-shadow: none; } + +.stats-grid { gap: 14px; margin-bottom: 32px; } +.stat-card { + min-height: 122px; + padding: 19px 20px; + border: 0; + border-radius: var(--radius-md); + background: var(--surface-raised); + box-shadow: var(--shadow-card); +} +.stat-card::after { display: none; } +.stat-card.signal { background: linear-gradient(135deg, color-mix(in srgb, var(--cyan) 10%, var(--surface-raised)), var(--surface-raised) 72%); box-shadow: inset 3px 0 var(--cyan), var(--shadow-card); } +.stat-card strong { margin-top: 15px; font-size: 36px; } +.stat-card small { font-family: var(--font-body); font-size: 12px; } + +.dashboard-grid { gap: 28px; } +.dashboard-rail { gap: 18px; } +.rail-panel { border: 0; box-shadow: var(--shadow-card); } +.rail-panel:not(.radar-summary) { background: color-mix(in srgb, var(--surface-raised) 76%, var(--surface-low)); box-shadow: none; } +.job-card { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; } +.job-card:hover { transform: translateY(-2px); border-color: var(--divider-strong); box-shadow: var(--shadow-card); } + +.filter-console { + grid-template-columns: minmax(280px, 1.8fr) repeat(5, minmax(126px, .72fr)) auto; + gap: 12px; + padding: 16px; + border: 0; + border-radius: var(--radius-md); + background: var(--surface-raised); + box-shadow: var(--shadow-card); +} +.active-filters { margin-bottom: 18px; } +.explorer-shell { gap: 18px; } +.explorer-card { border-color: var(--divider); border-radius: var(--radius-md); background: var(--surface-raised); } +.explorer-card:hover { border-color: var(--divider-strong); background: color-mix(in srgb, var(--surface-raised) 96%, var(--cyan)); } +.explorer-card.is-selected { border-color: var(--divider-strong); box-shadow: inset 3px 0 var(--cyan); } +.explorer-intelligence { border-radius: var(--radius-md); box-shadow: var(--shadow-card); } +.intelligence-panel { border-color: var(--divider); } + +.pipeline-summary { gap: 12px; } +.pipeline-summary article { border: 0; border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--shadow-card); } +.pipeline-board { grid-auto-columns: minmax(300px, 1fr); gap: 16px; scroll-snap-type: x proximity; scrollbar-gutter: stable; } +.pipeline-column { min-height: 420px; padding: 14px; border: 0; border-radius: var(--radius-md); background: color-mix(in srgb, var(--surface-low) 83%, transparent); scroll-snap-align: start; } +.pipeline-column-header { position: sticky; top: 0; z-index: 1; padding: 4px 2px 12px; background: inherit; font-family: var(--font-body); } +.application-card { border-color: var(--divider); border-radius: 10px; background: var(--surface-raised); box-shadow: 0 10px 28px rgba(0, 0, 0, .08); } +.pipeline-empty { border-color: var(--divider); border-radius: 9px; } + +.profile-index-grid { grid-template-columns: minmax(0, 1040px); } +.profile-index-card { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(250px, .5fr); padding: 0; border: 0; border-radius: var(--radius-lg); background: var(--surface-raised); box-shadow: var(--shadow-card); } +.profile-index-card.strong-match { box-shadow: inset 3px 0 var(--emerald), var(--shadow-card); } +.profile-index-content { min-width: 0; padding: 24px; } +.profile-index-card .intel-facts { margin: 22px -24px; grid-template-columns: repeat(4, minmax(0, 1fr)); border-color: var(--divider); } +.profile-index-card .page-actions { margin-top: 22px; } +.profile-index-art { position: relative; display: grid; min-height: 320px; overflow: hidden; place-items: center; border-left: 1px solid var(--divider); background: linear-gradient(145deg, color-mix(in srgb, var(--cyan) 4%, var(--surface-low)), var(--surface-low)); } + +.radar-illustration { --radar-x: 0px; --radar-y: 0px; position: relative; width: min(290px, 92%); aspect-ratio: 1; color: var(--cyan-dim); transform: translate(var(--radar-x), var(--radar-y)); transition: transform .32s cubic-bezier(.2,.8,.2,1); } +.radar-illustration svg { position: relative; z-index: 1; overflow: visible; filter: drop-shadow(0 18px 36px color-mix(in srgb, var(--cyan) 8%, transparent)); } +.radar-illustration-grid { stroke: color-mix(in srgb, var(--outline) 52%, transparent); stroke-width: 1; } +.radar-illustration-sweep { transform-origin: 160px 160px; } +.radar-illustration-signals circle { fill: var(--emerald); filter: drop-shadow(0 0 7px var(--emerald)); } +.radar-illustration-core { fill: var(--cyan); filter: drop-shadow(0 0 10px var(--cyan)); } +.radar-illustration-orbit { position: absolute; z-index: 0; inset: 13%; border: 1px solid color-mix(in srgb, var(--cyan) 15%, transparent); border-radius: 50%; } +.radar-illustration-orbit::after { position: absolute; top: 12%; left: 12%; width: 7px; height: 7px; content: ""; border-radius: 50%; background: var(--cyan); box-shadow: 0 0 14px var(--cyan); } +.radar-illustration .orbit-two { inset: 24%; transform: rotate(132deg); border-color: color-mix(in srgb, var(--emerald) 13%, transparent); } +.illustrated-empty .radar-illustration { width: 180px; margin-bottom: 8px; } + +.pill { border-color: transparent; border-radius: 999px; } +.empty-state { border-color: var(--divider-strong); border-radius: var(--radius-md); } + +@media (max-width: 1450px) { + .filter-console { grid-template-columns: minmax(250px, 1.5fr) repeat(3, minmax(135px, 1fr)); } + .filter-console > :nth-last-child(-n+3) { grid-row: 2; } +} + +@media (max-width: 1180px) { + .filter-console { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .filter-console > * { grid-row: auto !important; } +} + +@media (max-width: 820px) { + .cockpit-main { padding-top: calc(var(--topbar-height) + 30px); } + .mobile-tabbar a { font-family: var(--font-body); font-weight: 650; } + .filter-console { grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 13px; } + .filter-console .grow { grid-column: span 2; } + .pipeline-board { width: calc(100vw - (2 * var(--content-gutter))); grid-auto-columns: minmax(280px, 84vw); } + .profile-index-card .intel-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .profile-index-card { grid-template-columns: 1fr; } + .profile-index-art { min-height: 230px; border-top: 1px solid var(--divider); border-left: 0; } + .profile-index-art .radar-illustration { width: 220px; } +} + +@media (max-width: 560px) { + .cockpit-heading { margin-bottom: 22px; } + .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; margin-bottom: 24px; } + .stat-card { min-height: 92px; padding: 13px; } + .stat-card strong { margin: 9px 0 2px; font-size: 28px; } + .stat-card small { overflow: hidden; font-size: 10px; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; } + .kpi-label { min-height: 22px; margin-bottom: 0; font-size: 9px; line-height: 1.2; } + .filter-console { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .filter-console .grow { grid-column: 1 / -1; } + .filter-console .button { grid-column: 1 / -1; } + .profile-index-card { padding: 18px; } + .profile-index-card .intel-facts { margin: 18px -18px; grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (prefers-reduced-motion: no-preference) { + @keyframes premium-enter { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } + .stats-grid > *, .dashboard-grid, .explorer-shell, .pipeline-board, .profile-index-grid { animation: premium-enter .34s ease-out both; } + .radar-illustration-sweep { animation: radar-scan 7s linear infinite; } + .radar-illustration-signals circle { animation: radar-signal 2.8s ease-in-out infinite alternate; } + .radar-illustration-signals circle:nth-child(2) { animation-delay: -.9s; } + .radar-illustration-signals circle:nth-child(3) { animation-delay: -1.7s; } + .radar-illustration .orbit-one { animation: radar-orbit 12s linear infinite; } + .radar-illustration .orbit-two { animation: radar-orbit 17s linear infinite reverse; } + @keyframes radar-scan { to { transform: rotate(360deg); } } + @keyframes radar-signal { to { opacity: .38; transform: scale(.72); transform-origin: center; } } + @keyframes radar-orbit { to { transform: rotate(360deg); } } +} + +@media (prefers-reduced-motion: reduce) { + .radar-illustration { transform: none !important; transition: none; } +} diff --git a/static/img/vacatureradar-docker.png b/static/img/vacatureradar-docker.png new file mode 100644 index 0000000000000000000000000000000000000000..13c23929b7e845e6f094317a226dcf13c955eda0 GIT binary patch literal 7111 zcmXY01z1yW)V~{}K}MI15@`XE5QYOBAxJ5W-~b63DK*KF65>#i4uL-Ynqw?|JWg-rq?yHP&Nh;9~#)fccg_@(uuiD6b%Zj)wBM z_o&?UALV~XPaCKizPL^yAZ}VlS^!XwV?1@FrqJ}h`ZoRmaN*;>8`SSp{r~{@@7+Rb znPVKb@=VHoCN27R}(&L_z(M3H;>Gogt3It zz1x}fAKp1V`n%mcX&ai?*qHuoU;vk&SBnUYNgn^O0tq1T1Uw@@hK|^(FGvKD3?c-xv%04?mG- zawEi)7|(Ji!NIycBFJx*Gz4im{>RU?uiBwFo8!G9A*{9Wv zBcF5UyS``g5pp`f~T$Gg8W=Q5#1P@;-o%OWke znAP<*VPWL0gb$oEc3p#VcGdQ?2GR?IzkRwF4Rv>a$q)%<+wPw{QrF zD?GWTbfX+C-jSD8w($Lz=W1PH8V81v$KqC<`(*`lrz&KXQPyjmS@#5eXIlL+S4XdP z9-l-85I(7Nh7u4~R#YAPCgewifyh(0m`+xEUloD=)^XUE@BlZdPG zr@6o9FoZvetNDVb?e07cnBsO|=zL}`Cz7z=^^IjObnfrb{KcZjx)@OQ*$1kY$icya zn_)tT4EIbk2dpc38$${utI2OGwC&Az&)+M0{0sw<`n%3BvH#A|WPv+^$U?Kq!R)_9 zz4>_kbD$u3K`_x(_)P&nfg@sz@jfi~Zqx9e_36V}Trwf~<9#mK13NkLP*-``?&f5t zf@+z2-gx`;A?N7^2a~GGA4z>cEw08v<-)78phSq~W-N-F60?RnH!OO532+kk-2Wg6 z{5hBuWdrKj-!dR>=rb;L3cc3^3mxqrhl2wyd?A|!L{aRP{Vr+_-T0vMPtf|f{QY@A zGKDK1$an{jTPa-17*h8neR8iLKN0L8pHqU_Z;m`UFSPbfG`p{^{u!ah9LQOY(*4>Cs$-(Gb%1!4IWtWRlcDtK>Cd(z^k8tQ%gs1_;Lm~;`7}Al z<~(ghpc=~~-YMfQ)W5UQzhq`=vzSh#NFSPwMDSd zXY2g?F;bAHk4@_oswKr{u#ai-;MXjF zEOy!KTqaJD4XgU#IEao0?*p_C<;FI(>S(nFz@lu}AC6oe?dC0k)?9V0 zl4S2*{Y$_P#|bxahZ{Vh#Vb>YC-%>!E~$a&yRKPpOxku6w*=2t`=Hh@AUjO7pQFb>ZM1pq?y%026ie#F9DOcWz()xgUym1e4YU~_u=z|*{SqD z&w(_Bi1%kwavB%zB=~#P+1kpu?txL^!LtfW+XcfH?W$~OC?O~ra$(op5&1eJlI?d) z%gv?42AcObf`{%n*Kaqs+-RR*<|r%*%C zd}qW_<4dCTwSn&ttiAV#ui6@^1NY;t<`SoJw8$}^#$9A?0rJD;Lz2mM>chx@zW6VW zgO+RF)-M1g6D*x=tYvkQ?Hm~v`9bKQj>p7X5)5VC8)?Z&QZ?X>bW&LF5~BmOuJ61S zbSbX3OcAy{DOaGg{B_KpoIrbc1!#RV%d8=Po;{gJ2YYColq(MtSuLXzitN`F7{2L` zyWYU>&x=S6+Uy8v3azhY~>RI*HqT zVXS)-j|BeHC%d41hDoYh@L5LXgwwhp$}XO1kTp@22fV?nIJ( zb{p?@H&!SZc@(LDP;ZAjTkks3G}gG4Y0Fo~XQU$QYMF!6_R5~7tddrN+*g~ zgX$Aj*;=t=RnJrU+6nv&2Bbj;pd;moHRc@!X)h)( zLq_uwGWx9XCKy@j_q$+QZa_|_YZ{|&><)ezwny=~w>SPd%5Yz| zxcQ%8VX?fMVE%f8yRlNlE(6~|-pZLyy3oXknFxU+opmn1%&6wBQb8r2|p?96&PZ8mlaWc2RG}Wui zGGhC;Ti2!vxNR{Q$$pgNk|T$+<~`_r7JODx#-2F*GHb`&oeJk*TTXyUnk-^0{OHK- zwJtxsVndnM?I_+fF>U%NDjz&;t;>TiU(Kv2YB+-3(x%{OvM)b4C;=5Qb6ui7YW)L? z;Cuea9?{fk&y6}RY5>2VX(Q@JE^po+(>M|%_2l?4Z%-!sob}^APJ7mxKT6Y#`(6ZK z+I}ih{8`3FU?5%+@rjSb+f$-O)Z7dkoH3EJ&d3mNm_T~Y2In`5LffMB z*H?#`I&Uk^#+^%25UiY3dpveZp&sfFjR1Hw+HBMx7iu_mp8o6IQ$adF598ZwRW&Q7 z_3O0Fg5m(hL%La@J;uGnnt5BEPU%4Kvd2!mURZ_e0_@uh0Mq)@%;)jmAPGGbnOlCL z9p>}W>JQO2P6&%8A6C=OO)#}7RbyEJw!uZKe6Hk$z5<-^;^m*NoTIYRf*xbYQ^nLLk zNdTsbEDAJJw zr|mxnZ`$y{#G+yYJL!65@&Cz*WIK%)e6I1op3dR(+t3L`0;w;mIe)C6J zBR<}v7Ez)stn3SixkkLTEH{o(zA5f){To^a^jN+5!DuT{(+wJ-UQvV_eNldA*xKo3IoH4bOPFiaOJbNI~a9x-iSUnGU{BdkOBA3q2S;j7h?6b4{t z6B?+l6{0d{N7`xI-OOU(bGGh`PV64lwogR0B!*7Y1W%9{1$WtACZqhF1ez>C3FQXx zc%mnl%c-xygd0CPzNBR@B+VVPTfV#B`(Eqgd%41Z$L0OHZ@ffO1gn-?EJ15tZ7?xg zVC6#Tk~M9Dl9jzJP_Lm=5Qm;V39 zCCUzav&G+DNc}E=g-P2ber)yruj6jp>NR1Y?1r2Vyl=zIfDW+kw3c>I->r(%^!H-T z`+K@7A5ph5-(IwGw;)5sfieIk5NxvrQtK-ZCetBi%GqeEUEPoAZhq(LLAHTxD5w-J z=aY1UdoDXfG^aa zAzxWx!%A8gH_R&5G>d+_n1$u4OA7COKV_?{(NYd8p=9zK`Eg&;by@2V9C~eKb+lD; zjln5#N_h5#=dC}Y0qd58klMWGeV2@5gU8I zBz8hQ1S+klXALzrG#-au5^fR#Cam;ys+g-u9{GgJNCB^#I8PMQcGZv^BOLZ|Zle&#d^}pCxi=|NB9SiGZ(8%5{#1VH5} zh)g66PEQoQhA>MW91!X9Gba_3cmGs{vWcYUI5u4ZCRBl#;ZQdrYiUmQ>HCU`{fR=E z6GQfi<-s!62V^rMr?w$@o|QHs)5S(H@^$+5qJw(I~JOViduD-KVW_$}`oQr5cC_2XFB<7G-ek^A{8x)LSOF6IaG zm7oX*Ie0rTA7<8Qe=Q*+IC$ps*IVd4mTN8Dl}aE0x0q8NnEga^S$?f}TZO@i#{tyo zRRs2gt>2p_OZYJq>wtFkKj@Ttz+>#`Y22h68J(72{rOGfBk{Eho-WC1_u}J2N;UXP zPiw5|>zPf=trMZY_WtqMYn6eQXQ)>%>15m=p{l*+{Zi?oECXO|=YR7*?yfsRJy5{v zC9kxHGMxJF>f?UI+r1F{$d!N7RjR1tZUMk&Lj|*p!MIR3#QSJ^0Nf^l8`~5vYEY(> zR(dX%dmd6$f;_d%wQlu@dOc=xo&CNo-pm0rJ8(vpjkh7yJg)N^SAv0w(iG?xwI@|&b|<&vC{5z-i8XnMI+{2(JUtriDPv5 zy2jqRY^I&Qn|-$G5TE6419S~Arvw#h1b63wC;Jju;|`70!00enF8A_nB{!>RICt20?;!0=6-G}f0_;9z=V~Lj2FZQqy8tvG389#yT z8`Qe~p3X=2h&LaOh~>^g0N>GlxovYExPSOM4^uq;I@U0TTWK<6%72E%zN5WD4+PX= zh0@9m5fGZCcTJL@y{GSD+2tcfG7YSvI=wyVMEl2Dcs==w!X)U5j!np(gxLQPfS5`TD9*(i^m zl}{e^JZsV`$eL6yBkgztSq8H*TgKU2(gxOkf zqv@Tm_$6I$vuZB#UQXvoq$+xr8h|%~>ETg)=~T{LhA8{t2H~v}1o992x9@}8j;Awe zCom^^FfhBJtqEDVz?77^^EMr0qH4$r?Bx1J2NWYwuw!FUaG?yZ-G0Pv8Or>8kdle2 zlX2}b*B-0vM?L#9GDSIoV%_YIm66GYO$}_5+S@j3%|;pH5l^hM0Zn9=#ONlY2w#+@ z>liHYvx<(z02?KxvbDFtlTGPGnGUFSLDo&f)T{Q$Z*p!GVBmQ;sp--#RR%%?n`0dy z6#NA|-|qN;ySa0yC8%gtIrsIQfKkx27$bnnH3OAE#NO}Ilxlr)G*3fBWmq;4>|V*( z142$CT~~IMIrvqX=5!u}U5?rtd1wEQk8;MJr_rrZWM^u-^7jj^X42c<_{+RxE;cMn zqRf>q-FQ^^htHq=X+l|wG8_{>)sp-#0V;`7ax=7wu#~GAJQ5H3|Fu(;#3YdXEX$Lu}Vtx$$Ed{Ut@MX(`nvG(ge>_p?7{cQE9{wOL}0mmw*T;CV5+~E@1a`L=ayM=rhT>RbNIMol<8XCuiba6ad($vZ&nmn(*=TLC4uy46wmEej2 zD%VhhV2fTwQ3^J1!W6DtsDg#g!}wiou{v`A;qXUXHzF_F-r7{89=yht9N zeU9T$bdJ24td9m8mM|kgmQVv~g(zt4E|GLyM?AN-GaC+!g9DN%7;Bstkb~dfcaDkV zuonVBLkl7Edw%sDh-2zeI0d!kzKX@+C=CX_(`a`XJ@)6&9|B8C!A(ExmFGZ$#&o+R zkdYyHozi(|!E@_;)CYQvy=O>EMxc2i*;I0oJKqMQu^3>#FF`I4=j+$&yDKBS;H-J$dKRPJwLNCL5$&(LS?3t2ayYi$Z4FzJB33eMA3WXTRmT{Rif*q>v~!>S-2 z9+~u4|1J>7y-hdDXEU|))A@&-FX8Vqb_QWvY*1txsUWP}s};)J7^0@KzWyW~tljiE zv4&iOcRE{q#z-gp<^jRLyXP%l4d&~Eq4XGB+Fb02JL;+xwomBI1@33A;YHyJGif zWrW2E%)rE82D>~bfP0le2RBOPih3+niYQ27Mx{e&Za&5T{)*q}A>L!)ELPq%ZRo`=$>l3Fm-+)^> L#>g6NRP_G { + illustration.addEventListener("pointermove", (event) => { + const bounds = illustration.getBoundingClientRect(); + illustration.style.setProperty("--radar-x", `${((event.clientX - bounds.left) / bounds.width - .5) * 10}px`); + illustration.style.setProperty("--radar-y", `${((event.clientY - bounds.top) / bounds.height - .5) * 10}px`); + }); + illustration.addEventListener("pointerleave", () => { + illustration.style.removeProperty("--radar-x"); + illustration.style.removeProperty("--radar-y"); + }); + }); + } + document.addEventListener("keydown", (event) => { if (event.key === "Escape") closeNavigation(); if (event.key === "/" && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName || "")) { diff --git a/static/js/login.js b/static/js/login.js new file mode 100644 index 0000000..408deba --- /dev/null +++ b/static/js/login.js @@ -0,0 +1,28 @@ +(() => { + "use strict"; + + const toggle = document.querySelector("[data-pw-toggle]"); + const password = document.getElementById("id_password"); + if (toggle && password) { + const eye = toggle.querySelector("[data-eye]"); + const eyeOff = toggle.querySelector("[data-eye-off]"); + toggle.addEventListener("click", () => { + const show = password.type === "password"; + password.type = show ? "text" : "password"; + toggle.setAttribute("aria-label", show ? "Wachtwoord verbergen" : "Wachtwoord tonen"); + if (eye && eyeOff) { + eye.hidden = show; + eyeOff.hidden = !show; + } + }); + } + + const form = document.querySelector("form.auth-form"); + form?.addEventListener("submit", () => { + const button = form.querySelector("[data-submit]"); + if (!button || button.dataset.busy || !form.checkValidity()) return; + button.dataset.busy = "1"; + const label = button.querySelector("[data-label]"); + if (label) label.textContent = "Bezig met inloggen…"; + }); +})(); diff --git a/templates/components/radar_illustration.html b/templates/components/radar_illustration.html new file mode 100644 index 0000000..7e838d0 --- /dev/null +++ b/templates/components/radar_illustration.html @@ -0,0 +1,14 @@ + diff --git a/templates/profiles/list.html b/templates/profiles/list.html index b441000..eb5469d 100644 --- a/templates/profiles/list.html +++ b/templates/profiles/list.html @@ -1,3 +1,21 @@ {% extends "base.html" %} {% block title %}Zoekprofiel · VacatureRadar{% endblock %} -{% block content %}

Persoonlijke radar

Zoekprofiel

Je harde grenzen blijven deterministisch; ervaringsduur is alleen context en nooit een scorefactor.

{% for profile in profiles %}
{% if profile.is_active %}Live{% else %}Pauze{% endif %}

{{ profile.name }}

{{ profile.desired_titles|slice:":3"|join:", "|default:"Nog geen rollen geselecteerd" }}

Postcode
{{ profile.home_postal_code|default:"—" }}
Radius
{{ profile.max_distance_km }} km
Skills
{{ profile.desired_skills|length }}
Versie
{{ profile.version }}
Radar configureren{% if not profile.is_active %}
{% csrf_token %}
{% endif %}
{% empty %}

Geen profiel beschikbaar

Initialiseer het persoonlijke profiel via de bootstrapopdracht.

{% endfor %}
{% endblock %} +{% block content %} +

Persoonlijke radar

Zoekprofiel

Je harde grenzen blijven deterministisch; ervaringsduur is alleen context en nooit een scorefactor.

+
+ {% for profile in profiles %} +
+
+
{% if profile.is_active %}Live{% else %}Pauze{% endif %}
+

{{ profile.name }}

+

{{ profile.desired_titles|slice:":3"|join:", "|default:"Nog geen rollen geselecteerd" }}

+
Postcode
{{ profile.home_postal_code|default:"—" }}
Radius
{{ profile.max_distance_km }} km
Skills
{{ profile.desired_skills|length }}
Versie
{{ profile.version }}
+
Radar configureren{% if not profile.is_active %}
{% csrf_token %}
{% endif %}
+
+
{% include "components/radar_illustration.html" %}
+
+ {% empty %} +
{% include "components/radar_illustration.html" %}

Geen profiel beschikbaar

Initialiseer het persoonlijke profiel via de bootstrapopdracht.

+ {% endfor %} +
+{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index 5d554cf..17f54e2 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -118,32 +118,6 @@ - + diff --git a/tests/e2e/test_vr114_browser_and_accessibility.py b/tests/e2e/test_vr114_browser_and_accessibility.py index e2ce92c..4426854 100644 --- a/tests/e2e/test_vr114_browser_and_accessibility.py +++ b/tests/e2e/test_vr114_browser_and_accessibility.py @@ -101,6 +101,16 @@ def _run_html_accessibility_probe(client, user, profile, job) -> None: response = client.get(reverse("profiles:list")) assert response.status_code == 200 _assert_basic_html_accessibility(response.content, page_name="profiles-list") + profile_body = response.content.decode("utf-8") + assert 'class="radar-illustration"' in profile_body + assert 'aria-hidden="true"' in profile_body + assert "data-radar-illustration" in profile_body + premium_css = (Path(__file__).resolve().parents[2] / "static/css/premium.css").read_text( + encoding="utf-8" + ) + app_js = (Path(__file__).resolve().parents[2] / "static/js/app.js").read_text(encoding="utf-8") + assert "prefers-reduced-motion: reduce" in premium_css + assert 'matchMedia("(prefers-reduced-motion: reduce)")' in app_js response = client.get(reverse("profiles:edit", kwargs={"pk": profile.pk})) assert response.status_code == 200 diff --git a/tests/integration/test_auth_login.py b/tests/integration/test_auth_login.py index 19ed253..e18180b 100644 --- a/tests/integration/test_auth_login.py +++ b/tests/integration/test_auth_login.py @@ -23,6 +23,8 @@ def test_login_page_renders_stitch_design(client): assert "cdn.tailwindcss.com" not in body assert "https://fonts.googleapis.com" not in body assert reverse("password_reset") in body + assert 'src="/static/js/login.js?' in body + assert "