Lab Architect / Ellsworth

Afternoon Brief

Tuesday, September 29, 2026 — Twenty-first consecutive night with no nightly-branch build on any app. There is no feature/nightly-20260929 or feature/nightly2-20260929 branch, and git log --all --since="36 hours ago" is empty on all four app repos. Codebase health: 0 analyzer issues on all four apps. Buildr+/Closr+/Inspectr+ still hold 4/6/7 unpushed commits. All three of today's morning-brief ideas are confirmed in code and unbuilt. The most serious is in Closr+: a Procore sync where every insert fails still stamps "Last synced <now>" on the link (Recommendation #1). The DailyReportr+ "1 activity" badge turns out to be copy-pasted into two near-identical report cards, so fixing it once means extracting the shared card (Recommendation #2). Correction to the morning brief (fifth time): it again gives b983e74 and af49e33 (Aug 11) as the latest Buildr+ and DailyReportr+ commits. The real main heads are a7a5a03 and 0433f44.

Last Night’s Builds

No nightly-branch build on any app, twenty-first night running missed

Ran git fetch origin on all four repos. No new nightly branches exist; the most recent on any remote are still feature/nightly-20260908 and feature/nightly2-20260909. Yesterday's assignments (Dominique → Buildr+ Deletes That Wait for the Server, Katey → DailyReportr+ Admin Reports That Admit Failure) were not picked up. Today's morning report shows Hermes up and the Ellsworth nightly-review log last completing on Sept 6. Its log tail still shows the ~/Documents/App Dev/ paths from before Sept 5. The 00:15 and 01:15 builder launch jobs are still the thing to inspect.

Buildr+ 4 commits unpushed · working copy off main

Local main head a7a5a03 (Sept 21) is 4 ahead / 0 behind origin/main (33c825f). The working copy is still on fix/project-card-contract-value-spacing. Unchanged.

Closr+ 6 commits unpushed · tracked node_modules

Local main head 0925b0b (Sept 21) is 6 ahead / 0 behind origin/main (7a3ca21). The deleted-but-tracked node_modules/ tree still fills git status. Unchanged.

Inspectr+ 7 commits unpushed

Local main head 8145093 (Sept 21) is 7 ahead / 0 behind origin/main (4140149, Sept 8). origin/main has been silent for 21 days.

DailyReportr+ silent 15 days · working copy off main

main = origin/main = 0433f44 (Sept 14, 3.8.1 TestFlight prep). The working copy is still parked on the Aug 11 fix/owner-report-media-web branch with modified tracked files.

Codebase Health

App Analyzer Issues Breakdown Last 3 Commits (local main)
Buildr+ 0 dart analyze lib on a git archive main snapshot: "No issues found!" Still open: 10 of 10 swipe-to-delete pages delete in onDismissed with no error handling, and the filtered "No projects yet" empty state. a7a5a03 merge harden-ios-release-gate • a2873c3 merge project-card spacing fix • fac04bd source protected secrets file (unpushed)
Closr+ 0 "No issues found!" New: project_link_service.dart:242 calls _markSynced unconditionally after collecting per-item errors. There are 0 tests covering ProjectLinkService. Still open: silent catch (_) in the portfolio subcontractor provider, tracked node_modules/, and the in-app Procore secret. 0925b0b merge harden-ios-release-gate • d4c8952 gate TestFlight IPA on client IDs • ca33394 bump TestFlight 2.32.75 (unpushed)
Inspectr+ 0 "No issues found!" Raw Colors.green: 29 (unchanged, no commits). 8145093 merge Google sign-in client-ID fix • 5e0849b merge harden-ios-release-gate • 744cc12 add iOS artifact gate (unpushed)
DailyReportr+ 0 "No issues found!" on a git archive main snapshot. New: _buildReportCard is duplicated between reports_page.dart:740 and daily_report_page.dart:773, and both copies carry the fake activitiesCount. Still open: the admin-load retry loop, 40 raw Colors.green, and no sync-receipt tests. 0433f44 prepare 3.8.1 TestFlight • 2495d04 merge keyboard-recovery fix • 7253e65 stop keyboard recovery stranding text field (pushed)

Analyzer counts: Buildr+ 0 · Closr+ 0 · Inspectr+ 0 · DailyReportr+ 0. Buildr+ and DailyReportr+ were analyzed on git archive main snapshots because their working copies are off main; Closr+ and Inspectr+ were analyzed in place on main. The analyzer is clean again, but today's findings are the kind it can't catch: a success stamp written on a failure path, and copy-pasted UI that drifts.

Recurring Themes from Morning Briefs

Sources: the Sept 29, 28, and 27 morning briefs (briefs/).

Feature Ideas + UI/Code Health Recommendations

There are 3 new recommendations: 2 UI/code health items and 1 feature. All three came from today's morning brief and were verified against main with git grep and git log --all. The related history: Closr+ 9be3d6d "hardened Procore sync" added error collection but kept the unconditional mark-synced. Inspectr+ d19669d Drawing Reference Chips and 8f1dc0a revision-impact triage added sheet refs and a text-match heuristic, but no revision field. There's no DailyReportr+ commit touching activitiesCount. Yesterday's recommendations (Buildr+ server-confirmed deletes, DailyReportr+ admin failure state) are still open and still outrank these.

1. Don’t Stamp a Partial Procore Sync as Synced Closr+ — UI/Code Health

_syncProcore in lib/services/project_link_service.dart wraps each Cloudflare resolve-flag, insert, and update call in its own try/catch and appends failures to errors. It then calls await _markSynced(link.id) (line 242) unconditionally, which writes last_synced_at = now to the link. If 40 of 40 inserts fail, the link card in connected_sources_page.dart:285-288 still reads "Last synced Sep 29, 12:04 PM" permanently. The only failure signal is a transient in-memory message showing errors.first (line 218). It disappears when the page closes and never says how many items failed. SyncResult.syncedAt is also DateTime.now() on every path, including the outer "Sync failed" catch.

Why now: it's today's morning-brief idea #1. It has the same bug shape as the DailyReportr+ "Sync Complete" receipt (Sept 27), so fixing it here sets the pattern for both. Procore's iOS Inspections sync header shows status and progress (first reported Sept 24), and Bluebeam keeps unsynced markups in a visible Pending Updates panel. Neither shows a success timestamp for a failed batch. ProjectLinkService has 0 tests today, so the fix leaves the code better covered than it found it.

Sketch: Call _markSynced only when errors.isEmpty. For partial success, write last_sync_attempt_at and last_sync_error_count instead, or keep it client-side if the D1 column isn't worth a migration yet. Make SyncResult.syncedAt nullable and leave it null on any error. In the UI, replace errors.first with "⚠ 12 of 40 items failed — Retry" plus an expandable list, and color it with AppColors.primaryOrange (already used). Extract the API client behind an interface so ProjectLinkService can take a fake. Add tests for: all succeed (stamped), mixed success (not stamped, count correct), token expired mid-batch (not stamped), and total failure (not stamped).

2. One Report Card, No Fake “1 Activity” DailyReportr+ — UI/Code Health

Both lib/pages/reports/reports_page.dart:750 and lib/pages/daily_reports/daily_report_page.dart:783 compute activitiesCount = report.workPerformed?.isNotEmpty == true ? 1 : 0. They render "1 activity" whenever the free-text narrative is non-empty, no matter how many activities it describes. DailyReport only has the workPerformed string (no structured list), so the number can never be anything but 0 or 1. It's a presence flag dressed as a count. The two _buildReportCard methods are near-duplicates (about 100 lines each: date, project name, hours, photo count, and the badge), so any fix has to be made twice and will drift. Meanwhile, the weather-delay evidence pack (weather_delay_evidence_pack_service.dart:708) prints a real ${workLogs.length} activities, so the same word means two different things in one app.

Why now: it's today's morning-brief idea #2, and grep shows the duplication the brief missed. Extracting one shared widget removes about 100 duplicate lines and fixes the badge in one place. That's a net-cleaner change, exactly what the 50/50 standard asks for. It's also low risk and needs no data or D1 change.

Sketch: Create lib/widgets/report_summary_card.dart (ReportSummaryCard({report, projectName, onTap, onLongPress})) using context adaptive colors instead of the passed-in isDark. Replace the badge with a "Work logged" chip (icon plus text, no number) when workPerformed is non-empty. Only show a count if a structured list is added later. Swap both call sites. Widget tests: an empty narrative shows no chip, a narrative shows "Work logged" and no digit, and photo and hours counts still render. Golden-free, so no CI setup is needed.

3. Revision Label on Drawing References Inspectr+ — Feature

DrawingReference (lib/models/drawing_reference.dart:66) stores the ref type, sheet number, detail, and spec section, but not the revision the inspector was looking at. The D1 drawing_references table (0001_inspectrplus_foundation.sql:146) has no revision column either. Meanwhile, triage_queue_provider.dart:351 already flags items with "Drawing sheet referenced; confirm latest revision is reflected" based on a regex over free text. The app asks the user to confirm a revision it never recorded. An optional revision label (for example "Rev 3" or "2026-09-12 ASI-4") makes that check answerable, and the chip reads A-101/5 · Rev 3.

Why now: it's today's morning-brief idea #3. Procore iOS Compare Drawings and Fieldwire's "Outdated Version" watermark (both cited this morning) treat revision identity as basic. Procore's new punch-pin creation inside the Document Management viewer pre-fills document context. This is the smallest honest step and doesn't try to build a plan viewer.

Sketch: Add migration 0016_drawing_reference_revision.sql with ALTER TABLE drawing_references ADD COLUMN revision_label TEXT (nullable, so old rows stay valid). Pass it through the Cloudflare create/read handler. Add String? revisionLabel to the model and its fromJson/toJson/toCreateJson, an optional text field in the reference form, and a · Rev X suffix in chipLabel. Include it in PDF/detail output. Tests: JSON round-trip with and without a revision, chipLabel variants, and a legacy row with a null revision. Don't claim sync with Procore, Fieldwire, or Bluebeam.

UI Inconsistencies

Competitive Intel

Tomorrow’s Build Suggestion

This is the twenty-first night without builders, and the assignments are held from yesterday on purpose. Reassigning daily hasn't helped, and both tasks still outrank today's new items. Owner actions before either build: check the 00:15 and 01:15 builder launch jobs, and push the unpushed main commits (Buildr+ 4, Closr+ 6, Inspectr+ 7). For Closr+, stage explicit paths rather than using git add -A.

Dominique → Buildr+

Task: Deletes That Wait for the Server (Sept 28 Recommendation #1). Build the shared ConfirmDeleteDismissible, migrate projects_list_page.dart and then the other 9 pages, switch to AppColors.primaryRed, and add widget tests. Rationale: Buildr+ hasn't had a nightly feature since Aug 5, and a destructive action that can hide a live project is the highest-stakes honesty bug on the list.

Katey → DailyReportr+

Task: Admin Reports That Admit Failure (the retry loop, Sept 28 Recommendation #2). If time remains, do today's One Report Card extraction (Recommendation #2), which touches the same reports_page.dart. Rationale: it's the stalest app (15 days on main), and an unbounded request loop when offline is the most urgent defect in it. Closr+ partial-sync honesty (today's Recommendation #1) is queued next for whichever builder frees up first.