v2610.19.0 — October 19, 2026
MRs: !2569, !2523, !2532, !2541, !2542, !2543, !2544, !2549, !2551, !2552, !2555, !2556, !2557, !2560, !2561, !2562, !2564, !2566
Highlights
The valuation tier a customer picks in the coverage email is now preselected on their Kansas City moving contract (!2556, closes #7140) Kansas City’s new paper moving contract reads the tier the customer clicked in their coverage-confirmation email and preselects that election, so what they agreed to is what they sign. The tier is also recorded on the job, and tier pricing no longer drifts between the email and what gets charged. New Kansas City moving-contract template ships with it.
Virtual estimates confirm themselves when the customer replies (!2523, closes #7073) When a customer replies confirming their virtual estimate appointment, the appointment is now marked confirmed automatically — no staff click needed — and Yembo is told the customer confirmed. Replies by text (Dialpad, Twilio, OpenPhone, RingCentral) and email both wake it up, so it works for every franchise’s default text provider.
Book an estimate right from the moving request form (!2549, closes #7098) Zones can now let customers book an on-site estimate directly from the intake form, alongside virtual estimate booking. When only one is offered it opens straight into it; when both are, the customer chooses.
Jobs and leads lists stop failing to load (!2569, closes #7174) Two database queries that grew without bound — a calendar bulk-edit cleanup and the artifacts count — were wedging the production database during busy evening hours and taking the jobs/leads lists down with 504s. Both are now bounded to their own records.
Performance
- Bounded the two unbounded Neo4j scans that were wedging prod: the bulk-edit location delete is anchored on the edited events (it previously scanned every HAS_LOCATION relationship in the graph, ~100s per edit, unanchored), and
artifacts.list’s count is now a bareCOUNT(DISTINCT a)instead of re-running the author/related fan-out before counting (!2569)
Features
- Kansas City moving contract template preselects the valuation tier the customer clicked in the coverage email; the selection is also recorded on job metadata and tier pricing is charged in the correct units (!2556, closes #7140)
- Virtual estimate appointments can be confirmed from customer replies: a
confirm_eventheartbeat tool confirms booked VOSEs only, and confirming syncs to Yembo (status: confirmed+consumerConfirmedAt) from heartbeat or “Mark Confirmed” (!2523, closes #7073) - Book an on-site estimate (OSE) from the intake form alongside virtual estimates (VOSE), driven by the
osEstimators/virtualEstimatorszone configs; customers choose when both are offered (!2549, closes #7098) - Charlotte paper forms now in Aegis: declaration of value, release of liability on a second release slot, and RTA furniture liability (!2552, closes #7131)
- Aegis document templates now declare their own document kind, so a template can be slotted into any config slot rather than one hard-coded per kind (!2543, closes #6379)
- Additional stops/addresses are now shown on all of Apex’s documents, not just the estimates (!2532, closes #7083)
Fixes
- Jobs/leads lists intermittently returning 504s during busy evening hours — two multi-minute Neo4j scans starved the connection pool in front of user requests (!2569, closes #7174)
- Dialpad reconnect timestamps were stored 1000× too large, so a reconnected call could read “returned 49 days later” (!2566, closes #7172)
- Invoice dashboard filters now survive a page reload (!2564, closes #7167)
- BlueSage no longer refuses a terminal completion the operator already cancelled, which had left jobs stuck; voided invoices are excluded in the invoice query itself (!2560, fixes #7129)
- BlueSage terminal payments on uninvoiced jobs are now filed as deposits, so the job balance reflects the payment (!2557, closes #7139)
- Calgary zone: TV valuation raised to $50 per TV (!2551, closes #7136)
- Document create dialog separates document selection from preview, and preview neighbours are ranked in checklist order (!2555)
- AI phone receptionist’s job lookup now only considers jobs with activity in the last month (!2542)
Other
- Job-scoped GraphQL subscriptions now require a
jobIdfor scoping, with real filter tests (!2544, closes #5142) - Virtual-estimate contact-method markdown helper extracted in intake (internal refactor) (!2541, closes #6555)
- Sisko audit: Datadog daily drivers promoted out of
/tmpinto the skills dir (!2561, closes #31) - ttyd-web lockfile: refreshed vite-plugin-singlefile to 2.3.4 and pruned the orphan braces/micromatch tree (4 high → 1 high) (!2562)
v2610.18.4 — October 19, 2026
(no MR — production hotfix for the 2026-10-19 morning outage: backend pods silently wedging 12 minutes after the v2610.18.2 deploy, liveness-killed in 5–15 minute cycles, no error logs. v2610.18.3 was cut first but its pipeline failed its own lint gate before deploying anything — collapsed into this release.)
Fixes
- Capped
datesForLocalEventSpanat 400 days per event (with a logged warning naming the poison start/end): the new multi-day calendar publish iterated day-by-day with no bound, so one corrupt record (epoch-0 start, ms-vs-s timestamps, far-future end) spun the event loop for minutes and flooded Redis with a multi-MBCALENDAR_DAYS_MUTATEDpublish that stalled every pod at once — the load-amplified wedge behind “addresses/customer profiles not loading”. Clamped spans and the 1000-day publish cap now log to pod stdout so the offending records can be repaired. - Bounded every Redis client (transporter, cacher, pub/sub, driver) with
commandTimeout: 10sandenableOfflineQueue: false: with ioredis defaults, a stalled or dropped Redis queued commands forever with no per-command timeout — requests hung indefinitely instead of failing fast, which is why pods wedged without logging a single error. - Codified the backend liveness probe in the deploy template (it existed only as a hand-patch on prod) and tightened readiness from 30s×6 to 5s×2: a wedged pod now leaves the Service endpoints in ≤10s instead of taking user traffic for up to 3 minutes; replicas codified at 6 to match the outage-time headroom.
v2610.18.2 — October 18, 2026
(no MR — release-pipeline repair the same day as v2610.18.0: the three intake CI jobs were crashing npm ci)
Fixes
- The intake uploads fix from v2610.18.0 (!2505) had not reached the intake form because its release pipelines never passed — this patch lets intake deploy
Other
- Repaired the intake CI toolchain: node:20’s bundled npm (10.8.x) crashed in arborist’s peer-set loader with “Cannot read properties of null (reading ‘edgesOut’)” whenever it had to resolve
@lingui/babel-plugin-lingui-macro, which sat in the tree only as an optional peer and raced itself into a detached-node crash — nondeterministically, but the CI runners lost the race every time. It is now a pinned direct devDependency (5.9.0), and the intake jobs install npm 11.6.2 (verified deterministic locally: three cleannpm ciruns, green tests, and a green vite build on node 20) until node:20’s bundled npm catches up. Synced intake’s lock root with its package version.
v2610.18.0 — October 18, 2026
MRs: !2493, !2495, !2496, !2505, !2506, !2510, !2512, !2527, !2528, !2529, !2530, !2531, !2533, !2534, !2535, !2536, !2537, !2538, !2539, !2540, !2545, !2547, !2550, !2553, !2554. The Neo4j 5 cutover and the sign-in rework landed as direct pushes to app (MRs !1790 and !2439 remain open for follow-up work).
Highlights
Crew navigator keeps working when the signal drops (!2527, closes #7029) The navigator now queues the crew’s saves while there is no connection — step advances, undos, review marks, position stamps and inventory changes wait on the phone, survive an app restart, and are sent in order once the signal returns, with a “3 saves waiting” count on screen. The server merges each save instead of storing whole runs, so two saves in flight can no longer overwrite each other, and when ops moves or resets the crew while saves are queued, ops wins and the crew is told exactly what was dropped. Every step move and ops correction is recorded in the event’s history.
Recording a refund is fast again (!2533, closes #7071) The refund dialog’s duplicate-refund check searched every transaction in the system, so roughly one refund recording in three took 40–87 seconds and the slowest breached the gateway’s 60-second cut-off. The check now looks at the payment’s own job only, and any refund or payment recorded on a job appears in the Pay Invoice dialog immediately, without reloading the page.
Sign-in goes straight to the right place (direct push, commit 23d62efe41 — MR !2439 open for follow-ups) The legacy email-and-password login page is gone: the sign-in page now opens FusionAuth’s standard sign-in form and, on the way back, lands you on the page you were trying to reach instead of always the dashboard. This also removes the redirect loop that sent signed-in users to the sign-in page dozens of times in one session — a failed sign-in now shows an error card with a manual retry, and a genuine bounce is capped after three attempts.
Features
- Managers and admins in a zone a job is shared with can now tick that job’s Crew Pay boxes; shared Crew Pay saves through its own path, reports success or failure, and keeps newer edits made while a save is in progress (!2536, closes #6951)
- A new Reviews Page URL setting under Communications: estimate follow-up emails can include the franchise’s Google-reviews link, which the AI can share with customers (!2553, closes #7130)
- The intake form accepts moving-photo HEIC uploads from Chrome and other browsers that can’t read HEIC natively, by converting them before upload (!2505, closes #7031)
Performance
- The backend now runs the Neo4j 5 driver (5.28.3, still talking to the 4.4 servers while the cutover is staged), report and jobs-list queries were rewritten off the deprecated
size((pattern))scans onto counted subqueries, and migration 46 ships the matching indexes behind a server-version gate (commit 883448b55f, 2fc6ae177d, closes #7115)
Fixes
- Staff can always open and download a signed invoice whose refresh the backend refuses — the stored copy is served with an “outdated” banner in the preview and an “(outdated)” download file, while send and customer-facing views still refuse the stale document (!2534, closes #7068, #7054)
- The job’s History tab shows who created each Aegis document and keeps the history of deleted documents, with deletions naming every related job (!2538, closes #7085)
- Inbound call handlers ignore caller values that aren’t phone numbers — Avoca’s occasional “Yes” or sentence can no longer full-text-match job codes and attach thirteen different customers’ calls to one job (!2537, closes #7082)
- Signing in from a brand-new device works again when FusionAuth mints the token without the openid scope — the backend resolves the account by its FusionAuth user id before concluding “no user” (!2547)
- Creating a job as just a customer name and clicking Next → Next → Done no longer fails with “Can’t find job undefined in any zone” (!2554, closes #7096, #7137)
- Setting a rolling lock date on a franchise without that config key no longer throws (!2528)
- Multi-day events publish to every day they span, so dispatch and calendar day subscribers can’t miss the middle days, and a bulk edit also pokes the days the event used to occupy (commit 5ab3bc90dd)
- Automation rules skip events that don’t exist silently and warn only for a missing zone; the “Could not resolve event timings” warning fires once per event instead of up to 16,000 times a day (!2531, !2511)
Other
- Dependency security remediation:
@modelcontextprotocol/sdksemver bump and related audit fixes (!2510); pinned@move-right/aegisto the published 0.1.47 and prepped 0.1.48 for the registry mirror (!2529) - Neo4j 5 readiness: bounded migration batches for user-search normalization and stale-token cleanup, migration failure ledgers and completion receipts, Neo4j query/schema/integration test suites, a cutover rehearsal harness, and manager-side clear-database tests (commit 883448b55f, 2fc6ae177d, 4236b217f1)
- CI: backend build, frontend build, deploy-prod and deploy-dev share one resource group so a later pipeline can’t overwrite an image while an earlier deploy is still pulling (commit 5ab3bc90dd)
- Nightly backup hardening: docker/database backup failures now fail loudly and notify instead of exiting 0, and the SSH tunnel plus GCS uploads authenticate as a service account so the nightly path no longer needs interactive re-auth (commit a22198fcde)
- QA tooling: a phase-0 app-health gate with a bounded repair playbook for
/qa(!2530), per-session CDP mobile viewport emulation so the mobile pass can run (!2535), and behaviour-test suites plus run extensions for search, documents, roles and ymm-toledo (!2493, !2550) - Internal ops tooling: self-update guardrails for the agent fleet — RAM budget, memory gate, zram swap and sessiond wall (!2495) — with fleet-orchestrated nightly updates, gateway driving dax → odo → self (!2496), all fleet machines pinned to America/Winnipeg time (!2506), the mr-review-loop skill and nightly review cron (!2512), meeting sweep via Drive CLI with AI-takeability triage and the AI dev-loop skill (!2540), scheduled qa/pm/dev crons consolidated into the live checkout under a snapshot/restore harness (!2545), and the sisko bridge ported to pi-web’s server-plugin API v3 (!2539)
v2610.10.0 — October 10, 2026
MRs: !2176, !2480, !2518, !2522, !2524, !2525, !2526
Highlights
Create several documents at once (!2525, closes #7081) The Create Document dialog now has a checklist of every document type your zone can create. Tick several to create or send them together. Each one keeps its own options and its own message to the customer, and each is sent in its own email. Any document that is missing an option is flagged, and Send stays disabled until every document is ready. When it’s done, the dialog lists the result for each document, and you can resend or download any of them individually. On phones, a compact picker replaces the checklist so the preview stays in view.
Inactive leads and estimates close automatically again (!2524, closes #7074) Leads and estimates with no activity for the number of days set in your zone’s settings are closed automatically each night. Comments and upcoming events count as activity and keep a job open. Jobs that have already been inactive past your zone’s limit will close on the first night after this release.
Features
- A new automation trigger fires when an outbound auto-dial is dropped before it connects, so rules can follow up on calls that ended without a disposition (!2480, closes #7009)
Fixes
- Rules inherited from a parent zone are shown as read-only in a child zone, with a note explaining why they can’t be edited or deleted there (!2176, closes #6063)
- Bee’s Update Inventory works from the inventory as it is currently saved, and its reply states the saved totals, or says plainly when nothing was changed (!2526, closes #7072)
- OTR’s claims release keeps the repair scope within the field’s length limit (!2522)
Other
- Improved sign-in diagnostics (!2518)
v2610.08.0 — October 8, 2026
MRs: !2509, !2511, !2513, !2514, !2515, !2516, !2517, !2519, !2520, !2521
Highlights
Existing payments are applied to a new invoice automatically (!2520, closes #7018) When a job gets its first invoice, deposits and refunds already taken on the job are applied to it, so the invoice shows what the customer still owes without a manual step. When that can’t be done safely — another live invoice exists, or the payments don’t fit the invoice — they are left for you to apply, and the Apply Transactions dialog now lets you pick several at once.
Clearer pacing and forecast details on the calendar (!2519) The pacing tooltip now explains how projected revenue is calculated and which last-year figure it compares against, and says so when there is no last-year baseline. Forecast cards lead with predicted bookings and compare predicted jobs, trucks and crew with what is booked, alongside remaining capacity.
Features
- Bust a Move has bilingual French and English estimate, invoice and receipt templates (!2509)
- OTR’s claims release uses the new four-page Release of Liability and Settlement Agreement, with the loss date and agreement date filled in automatically (!2521, closes #7075)
- Karve admins can sign a user out of all devices from the user actions menu (!2517, closes #7070)
Fixes
- Closing a job is checked before any of its events are cancelled, and a job can no longer close while a pending, booked or confirmed event would stay active — so customers aren’t sent cancellation notices for moves that are still on (!2513, closes #7063)
- An estimate can only be created for events that have charges; the job page’s ”+ Estimate” action and the Create Document dialog both check this, including unsaved charges (!2514, closes #7048)
- When a job moves to a zone that uses a different Yembo workspace, the job is unlinked from the previous workspace and its upcoming virtual estimates are cancelled, with a comment on the job recording what happened (!2515, closes #7011)
- A declined card charge is now recorded as declined, and shows “Card declined — nothing was charged” (!2516)
- Admin password reset and password change actions are more reliable (!2517, closes #7070)
- Automation rules skip events that have no job instead of failing (!2511)
Other
- Internal QA documentation and development tooling updates (!2519)
v2610.06.0 — October 6, 2026
MRs: !2443, !2484, !2489, !2494, !2497, !2498, !2499, !2500, !2501, !2502, !2503, !2504, !2507, !2508
Highlights
Creating and sending documents now happens in one dialog, with a live preview (!2443, closes #6386, #6385, #7007) The old Documents modal and Send Documents screens are replaced by a single Create Document dialog: the template declares its settings on the left, a live preview of the customer’s sign page sits on the right, pricing updates instantly as you tick or untick events, and the dialog ends by creating and optionally sending the document. Estimates can also opt out of the deposit per document.
A Dual Pricing estimate can run alongside your existing estimate (!2507, closes #7061) Zones can now offer a dual-pricing estimate — card price and cash price with a pay-deposit button on the sign page — as its own document slot, without overwriting the current estimate. Existing estimates keep working until a franchise is ready to switch.
Returned abandoned and missed calls are now tracked per call (!2484) When a customer calls again after an abandoned or missed call, the original call is stamped as reconnected with the time it took, and new report columns expose this per call and per job for reporting. Report counts of abandoned-and-then-connected calls are now correct even when a call connects before the abandoned one in the record order.
Fixes
- The document preview no longer hangs on “Loading preview…” in Safari and other WebKit browsers, and a failing preview shows its error only for the preview that raised it (!2508, closes #7059)
- Starting an onsite estimate no longer fails over move-size parsing: values such as office square footage are no longer stored as bedroom counts or sent to the survey service, and its validation messages now surface to users and support (!2504, closes #7050, #7022)
- An intake address entered as just a street name is now looked up near the job’s other location, so a street-only start address can’t drag a lead across the country into the interstate zone (!2503, closes #7016)
- Estimator cancellation metrics on the sales dashboard no longer count customer no-shows, so the numbers reflect estimates where an estimate actually took place. Jobs can be closed as “Customer no-show (estimate)” at the lead or estimate stage, and dashboard drill-downs use filters that match the metrics (!2494, closes #7020)
- Aegis documents: brand colors render opaque so titles and labels can’t turn invisible, invoices no longer print $0 tax beside a taxed total when the Tax Exempt tag is added just before invoicing, and manual receipts report payment received only for paid transactions (!2501, closes #7044, #7042, #7043)
- The deposit field in the job form and the amount fields in the Take Transaction dialog select their value on focus, so typing replaces it instead of being swallowed by the $0.00 decimals (!2502, closes #7021)
- The sign page’s Pay Deposit card form now grows to the height the card form reports — including newly added fields like billing ZIP and validation messages — and scrolls if taller than the screen (!2497)
- A card charged from the Take Payment dialog (keyed in or on file) now carries the job’s reference with it to the payment processor, instead of arriving unattached (!2498)
- Jobs now move from Lead to Estimate for every estimate document type, including the two-signature estimate (!2507)
Other
- Dependency security updates (!2499)
- Reduced log noise for expected message-fetch errors (!2500)
- Internal tooling improvements: scheduled self-updates and snapshot backups for internal machines (!2489)
v2610.05.1 — October 5, 2026
Other
- Deployment tooling fix (commit 92e1dcab65)
v2610.05.0 — October 5, 2026
MRs: !2455, !2460, !2471, !2473, !2474, !2475, !2476, !2477, !2478, !2479, !2481, !2482, !2483, !2485, !2486, !2487, !2488, !2490, !2491, !2492
Highlights
Crew navigator: a round of improvements from the field (!2471, closes #6966) The crew navigator now uses the full screen on every phone and tablet, has larger buttons that are easier to tap on the move, and lets crews upload a photo they’ve already taken. Missing items keep their quantity and can be restored, new inventory items ask for an approximate weight and volume, and the optional customer signatures after loading and unloading have been removed to keep crews moving.
Bilingual invoices, receipts and estimates (!2490) New bilingual invoice, receipt and estimate documents are available, covering charges, payments, taxes, discounts and signatures, including tax-exempt invoices. A new estimate calculator builds estimates from a manual weight, the job’s inventory, storage data or a dwelling profile, and suggests crew size, labour, travel, specialty-item, fuel and packing charges.
Attach the PDF when sending a document (!2482, closes #7002) Franchises can now choose to attach the PDF to document emails, alongside the signing link, so customers who want a copy no longer need it sent by hand. The setting is per zone and off by default.
Performance
- Cancelled and completed revenue reports run much faster, and their run time no longer grows with the size of the date range (!2483, closes #6975)
- Faster role and user lookups across the app (!2475)
Features
- The Book Off time picker accepts typed times such as
14:00,730por2:30 PM, and suggests the matching time first (!2481, closes #6788) - Square Checkout payments can include remarks, which are passed to Square as the payment note (!2479, closes #6948)
Fixes
- Signed invoices stay openable and refreshable when a different event on the same job changes (!2485, closes #7034)
- ACH Info and W-9 toggles save from the Labor tracker and Crew pay table (!2474, closes #7001)
- Card payments: Delete is shown only to users who can delete transactions, and is unavailable on rows with an open pay link (use Cancel pay link instead). Declined payments are closed automatically (!2460, closes #6657)
- The job page and Dispatch handle a cleared customer name, and several edge cases on Workorders, Billing and Overview, without errors. Long Sales Agent and Estimator names no longer break the layout (!2491, closes #6784, #6633, #7037, #7038, #7032)
- The crew navigator records the payment method where reports expect it, so the Payment Method report column fills in (!2471)
- The Connected Applications card on the account page is shown only to users who can use it (!2478, closes #6712)
- Upload notifications show the file type, and document names use the current date (!2479, closes #7017, #6499)
Other
- Security hardening and dependency updates (!2455, !2476, !2487)
- Reduced log noise for missing document files (!2477)
- Internal tooling and build improvements (!2473, !2486, !2488, !2492)
v2610.01.0 — October 1, 2026
MRs: !2385, !2386, !2396, !2419, !2450, !2451, !2452, !2453, !2454, !2456, !2457, !2458, !2461, !2462, !2463, !2464, !2465, !2466, !2467, !2468, !2469, !2470
Highlights
Every configuration key now shows on the Settings page (!2419, closes #6978) The Settings page reads its configuration definitions from the backend instead of a hand-maintained frontend copy, so new franchise settings appear without waiting on a frontend release. The copy had silently drifted — 34 backend-defined keys were missing from the page, and all of them appear now.
Aegis documents appear on the customer page — and Resend works (!2452, closes #6928) Estimates and other Aegis documents now show on the customer’s Documents tab, and the Resend button delivers the signing link. Existing documents were linked to their customers automatically; internal-audience documents such as inventory sheets stay unsendable by policy.
Document emails reach customers who unsubscribed (!2461, closes #6767) Customers who unsubscribed from list emails were also silently dropped from emails carrying documents — the estimate never arrived and nothing on the job recorded why. Emails that carry a document or payment link now bypass the unsubscribe block, and franchises can turn this behaviour off per zone.
Fixes
- The Repeat Customer dialog on New Opportunity shows the server’s results as they are — it no longer re-filters them client-side and hides customers that match (!2469, closes #6535)
- “4015.0 Top Crew” and any report with a Crew Member Emails column previews again — crew members with no email are skipped instead of breaking the report (!2465, closes #6860)
- Report folders with numeric names (e.g.
/5020) open instead of returning 404 — legacy/reports/:reportIdURLs only redirect real report IDs (!2464, closes #6958) - Similar Jobs’ View All button opens the Jobs page with the search filter applied (!2463, closes #6927)
- The global search bar recovers from failed searches and is no longer stuck loading for agent roles (!2450, closes #6742)
- Jobs created from inbound Dialpad calls now land on the called number’s zone instead of defaulting to YMM (!2456, closes #6986)
- People → Employees: “Invite Another” actually sends the invite and keeps the dialog open (!2466, closes #6798)
- Delete is hidden from roles without
artifacts.deleteon the job’s Documents tab, not just disabled on jobs (!2468, closes #7019) - The People page breadcrumb follows the active tab (Customers / Employees / Crew) (!2462, closes #6950)
- Email actions like “Send reset password email” are disabled for users with no email address (!2453, closes #6684)
- Calculator scripts load one at a time, fixing QuickJS redeclaration errors like
WEIGHT_PER_CREW(!2451, closes #7010) - French estimate template: table headers and totals labels (Subtotal, Tax Total, Estimated Total) are now French in the Tap Tap FR template (!2470)
- The no-zone login path verifies a caller-supplied role is actually held before minting it (commit 39f442a010)
Other
- Permission rework completed: zone contexting and cross-zone search derive from role membership instead of permission edges, the legacy v3 permission-edge system and its
setPermissionsmigration were removed entirely, anddeploy-prodno longer aborts on the empty-role migration failure (!2396, closes #7014). Crew roles now also context into descendant zones (commit fc3cae5ed8), and materialized zone children stay strict (commit a34afca039) - Logging hardening:
Error.messageis serialized in every structured log path — the chronic empty-message gap is gone (!2458) — and non-retryable Neo4j session errors now log full context including code, message and recent queries (!2457) - CI: npm cache mounts, Chrome layer ordering and inline image cache cut frontend/backend build times significantly (!2386); the Aegis mirror now pushes to the public repo’s
devbranch on staging deploys — keeping production sign-page releases separate — with serialized runs and a stale-pipeline guard (!2385) - The module graph no longer defers environment evaluation through a
global.constants → jobs.componentimport cycle, fixing dev-serve crashes (!2454) - Internal tooling: .pi skills for the CodeRabbit MR fix loop, and GitLab review scripts list every unresolved thread (!2467, commits cf5af81e74)
- Changelog README notes the direct-commit citation fallback (commit d2a8a03b02)