Repository navigation
v1.151.0 — Dispatch warns when a job will not fit the tech's day
Warning: Take a database backup before updating. Migration 129 fills in missing dates on job photos, and migration 130 removes two unused photo columns. If any photo still has data in those columns after the copy, the update stops before removing anything and the app will not start until that data is moved by hand. Behind Cloudflare without nginx real_ip, set GDX_TRUSTED_UPSTREAM_PROXIES=cloudflare in .env, or every visitor is counted as a Cloudflare address for sign-in limits and the audit log.
Highlights
- Dropping a job on a tech's day on the dispatch board now checks whether the job's hours fit what is left of that tech's shift. When they do not, you can add a second tech, carry the rest over to the tech's next workdays, or keep it all on one day. A job that fits is scheduled the same way as before.
- Visits are now booked for the job's own hours. Before, an 8-hour job could be booked as a single 21-hour visit.
- The cost breakdown on a job now shows the labor variance.
Changes you may notice
- The "Mobile field view (preview)" button in the top bar now works as a toggle. Click it again to go back to the desktop page you were on.
- Technicians are now linked to a real user login. When you set up a technician, you pick their login from a list.
- When someone is taken off a job's crew, they lose access to that job's chat, quote and day summary on the phone.
- An invoice a tech sends from the truck now records which tech sent it.
- During an outage you get one error message instead of one for every screen that keeps retrying.
- Signing out after being idle now takes you back to the page you were on when you sign in again.
- The email and lock icons on the sign-in page no longer cover the text on a phone.
- Cards on the dispatch timeline stay inside their lane.
- A shop timezone that is not a real timezone name is now refused when settings are saved.
Under the hood
- More actions are now written to the audit log, including role changes (with the old role), user edits, crew changes (who was added and who was removed), inventory edits, vehicle inspections, diagnoses, hazards, receipts, and session and payroll settings.
- Assigning or removing a role now runs the same owner-only and permission checks everywhere, and assigning a new role replaces the old one instead of adding a second.
- Rate limits and audit entries now record the real client address, not the proxy's.
- An open dispatch board connection checks the sign-in again, so a deactivated user stops getting live updates.
- Several unused screens and connections were removed (an unused payroll summary and export, an old dispatch socket, and dead data-privacy code).
What's Changed
- chore: move .claude/{agents,rules,skills} to claude-config/ behind symlinks (GDXA-1417) by @freePerro in #1261
- docs(harness): CI job log proves a PG test ran via service log + durations (GDXA-1424) by @freePerro in #1262
- fix(job_access): crew join matches dashed job_assignments.job_id on SQLite (GDXA-1427) by @freePerro in #1263
- fix(harness): run_tests_split.sh skips a .venv that cannot run the suite (GDXA-1432) by @freePerro in #1264
- fix(mobile): truck invoice send goes out as the tech, not as nobody (GDXA-1369) by @freePerro in #1265
- fix(auth): role writers rewrite the stale UserRoleAssignment (GDXA-1453) by @freePerro in #1266
- fix(auth): assign_role replaces the user's assignment instead of adding one (GDXA-1464) by @freePerro in #1267
- test(officeAlerts): spec stops pinning the dead scope argument (GDXA-1479) by @freePerro in #1269
- fix(jobs): job activity dot uses the defined primary token, not undefined --accent-b (GDXA-1482) by @freePerro in #1270
- fix(dispatch): timeline cards stay inside the card and the lane (GDXA-1486) by @freePerro in #1271
- fix(frontend): an outage shows one error toast, not one per failing poll (GDXA-1490) by @freePerro in #1272
- fix(frontend): idle-timeout logout keeps ?redirect, no bare /login (GDXA-1492) by @freePerro in #1273
- fix(officeAlerts): stop passing the dead scope argument to OfficeAlerts.start() (GDXA-1480) by @freePerro in #1274
- fix(settings): session_policy and payroll config writes are audited (GDXA-1506) by @freePerro in #1276
- chore(photos): remove dead _user_name helper (GDXA-1475) by @freePerro in #1275
- fix(jobs): crew change audit rows name who was added and removed (GDXA-1455) by @freePerro in #1278
- fix(dispatch-ws): re-check token and user on an open board socket (GDXA-1440) by @freePerro in #1277
- fix(admin_ops): UserPatch/UserCreate.role normalized and vocabulary-checked (GDXA-1460) by @freePerro in #1279
- chore(ownership): tests/e2e/test_dispatch.py routes to jobs-dispatch (GDXA-1509) by @freePerro in #1284
- fix(login): email and lock icons no longer overlap the input text at phone width (GDXA-1496) by @freePerro in #1281
- refactor(dispatch): delete uncalled unassignJob from DispatchView (GDXA-1505) by @freePerro in #1283
- fix(payroll): source save reverts on rejection and confirms on success (GDXA-1519) by @freePerro in #1285
- fix(segments): segment customer list decrypts customers.address (GDXA-1528) by @freePerro in #1287
- fix(segments): active audience chip and customer link use defined theme tokens (GDXA-1481) by @freePerro in #1280
- fix(jobs): edit dialogs send only a crew they read; PATCH guards crew and lane on finished jobs (GDXA-1499) by @freePerro in #1286
- fix(integrations): disconnect is reachable; route_shadow_scan compares path shapes (GDXA-1523) by @freePerro in #1288
- fix(admin_db): drift check compares the Alembic Base too; nullability mismatch sets the verdict (GDXA-1537) by @freePerro in #1289
- fix(demo): seed writes the tech's crew rows so demo chat opens (GDXA-1551) by @freePerro in #1291
- test(dispatch): delete DISP-11, which asserted multi-tenancy via x-tenant-id (GDXA-1535) by @freePerro in #1290
- fix(voice): absent Whisper logs one INFO line, not a traceback per voice note (GDXA-1554) by @freePerro in #1292
- fix(mobile): a removed crew assignment no longer grants chat, quote or day-summary access (GDXA-1545) by @freePerro in #1293
- fix(demo): seed holding areas and park jobs so Move to… shows (GDXA-1454) by @freePerro in #1294
- fix(ci): check in gdx_ai_readonly/gdx_ai_write roles and run their tests in CI (GDXA-1564) by @freePerro in #1296
- docs(auth): denylist 503 docstrings say a Redis outage stays fail-open (GDXA-1563) by @freePerro in #1297
- fix(payroll): delete the uncalled /api/payroll summary, tech and export routes (GDXA-1560) by @freePerro in #1298
- fix(job-costing): show labor variance in the Cost breakdown dialog (GDXA-1561) by @freePerro in #1299
- fix(mobile-chat): chat reads jobs.assigned_to as a technician id; deleted roles not notified (GDXA-1541) by @freePerro in #1295
- fix(photos): fill job_photos.created_at from uploaded_at and default it on insert (GDXA-1575) by @freePerro in #1301
- fix(auth): role assign/unassign run the owner-only and delegation-cap checks (GDXA-1469) by @freePerro in #1282
- fix(inventory): PATCH applies only sent fields; part audit rows carry tenant, id and changes (GDXA-1578) by @freePerro in #1303
- docs(gdx-harness): a push is a Bash call of its own by @freePerro in #1304
- fix(job-costing): only the newest request writes, so A→B→A keeps good data (GDXA-1572) by @freePerro in #1302
- fix(technicians): require a real users.id for a technician; Login picker replaces the slug fallback (GDXA-1583) by @freePerro in #1305
- fix(auth): delete /ws/dispatch and its connection manager (GDXA-1562) by @freePerro in #1300
- fix(audit): role change and user update record the prior value (GDXA-1461) by @freePerro in #1268
- fix(photos): drop the never-written job_photos.content_type/file_size twins after backfill (GDXA-1587) by @freePerro in #1306
- fix(core): delete dead core/gdpr.py and the claims that it is live (GDXA-1586) by @freePerro in #1308
- fix(audit): vehicle inspection, diagnosis, hazard and receipt writes leave an audit row (GDXA-1591) by @freePerro in #1307
- test(fixtures): regenerate structure.sql from the ORM and guard it against drift (GDXA-1558) by @freePerro in #1309
- chore(repo): untrack generated e2e-route-report.json; CI fails on tracked-but-ignored files (GDXA-1595) by @freePerro in #1310
- docs(tests): tenant isolation schema test tells its xfail story as history (GDXA-1602) by @freePerro in #1311
- test(reports): drop the 3-table fixture check the whole-schema drift guard replaced (GDXA-1604) by @freePerro in #1312
- docs(chi): CHI has a REST API we cannot use, not no API (ADR-014, hubx plan) (GDXA-1610) by @freePerro in #1314
- docs(ux-audit): date-stamp the i18n entry with the scaffold deletion (GDXA-1606) by @freePerro in #1315
- fix(variance): remove dead pass after log call in variance_summary handler (GDXA-1639) by @freePerro in #1316
- style(plugins): eventlog init imports are one first-party block (I001) (GDXA-1636) by @freePerro in #1317
- fix(settings): refuse a timezone that is not an IANA zone name (GDXA-1640) by @freePerro in #1318
- fix(core): rate-limit and audit key on the real client, not the proxy or the header (GDXA-1597) by @freePerro in #1313
- fix(migrations): env.py lock, 001 and bootstrap_app run on SQLite; 002-065 still Postgres-only (GDXA-1615) by @freePerro in #1319
- feat(topbar): mobile preview button toggles back to desktop by @freePerro in #1320
- fix(install-sheet): drop dead pass after log call in install_specs handler (GDXA-1638) by @freePerro in #1321
- feat(dispatch): ask before a drop runs past the tech's day; visits use the job's hours by @freePerro in #1322
- test(warranties): build the schema from the ORM and assert create/delete/claim audit rows (GDXA-1629) by @freePerro in #1323
- fix(core): remove dead pass after log call in handler bodies (GDXA-1637) by @freePerro in #1324
- fix(lint): name conftest and tools first-party so I001 does not depend on the tree (GDXA-1628) by @freePerro in #1325
- fix(planner): read the shop timezone from AppSettings via the shared reader (GDXA-1622) by @freePerro in #1326
- docs(release): v1.151.0 release notes by @freePerro in #1328
Full Changelog: v1.150.0...v1.151.0