Lab Architect / Ellsworth

Afternoon Brief

Wednesday, September 30, 2026 — Twenty-second consecutive night with no nightly-branch build on any app. There is no feature/nightly-20260930 or feature/nightly2-20260930 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. Checking the Closr+ idea turned up a real date bug: a document that expires later today counts as neither "expired" nor "expiring soon", so it drops out of the warning banner on its last valid day (Recommendation #1). Correction to the morning brief (sixth time): it again lists 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-second 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. The held assignments (Dominique → Buildr+ Deletes That Wait for the Server, Katey → DailyReportr+ Admin Reports That Admit Failure) were not picked up. Today's morning brief adds a new detail: the Ellsworth nightly-review producer "was retired at Leonard’s request", so the missing review report is expected. That covers only the 04:20 reviewer. It doesn't explain why the 00:15 and 01:15 builders are silent, and those launch jobs are still the thing to inspect. ~/memory/last-nightly-build.md (the builder coordination file) was last updated Aug 5.

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). git status still shows 1,648 entries, almost all deleted-but-tracked node_modules/. 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 22 days.

DailyReportr+ silent 16 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 "No issues found!" New: ProjectCard is a bare GestureDetector with no Semantics wrapper, and there are 0 tests referencing ProjectCard. Still open: 10 of 10 swipe-to-delete pages with unguarded onDismissed, 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: CloseoutDocument.isExpiringSoon requires days > 0, while isExpired uses isAfter(now), which leaves a gap of up to 24 hours. There are 0 tests covering either predicate. Still open: _markSynced on partial Procore failure, the silent portfolio catch (_), and tracked node_modules/. 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). Still open: the revision field on drawing references and the "Photo N of M" markup label. 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!" New: ReportsPage builds every report card eagerly (SingleChildScrollView → Column → ResponsiveDesktopGrid, which is a plain Column on narrow screens). Twenty other page sites in the app already use ListView.builder or SliverList. Still open: the admin retry loop, the duplicated _buildReportCard, 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. All four apps were analyzed with dart analyze lib on git archive main snapshots (after flutter pub get --offline) so WIP and off-main working copies don't skew the counts. Each run took 3–4 seconds.

Recurring Themes from Morning Briefs

Sources: the Sept 30, 29, and 28 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. On history: Closr+ d62df44 added expiry tracking and 1c6c000/068f0bc added a separate warranty expiry page, but nothing filters closeout documents by expiry or fixes the boundary. Buildr+ has one earlier accessibility commit (9d7228b, paywall pill) and none on project cards. DailyReportr+ has no lazy-list or sliver commit for Reports. The held Sept 28 items (Buildr+ server-confirmed deletes, DailyReportr+ admin failure state) still outrank these.

1. Tap the Expiry Banner — and Stop Losing Docs on Their Last Day Closr+ — Feature (with bug fix)

_buildExpiryBanner (lib/pages/documents/documents_page.dart:288) counts expired and soon-expiring documents and shows "2 documents expired · 3 expiring within 30 days". It's a plain Container with no tap target, and the All tab only offers text search, so the user has to hunt for the documents it names. The counting also has a gap. In lib/models/document.dart, isExpired is now.isAfter(expirationDate), but isExpiringSoon requires daysUntilExpiration > 0, and Duration.inDays truncates. A certificate that expires at 5 PM today is at 0 days at noon, so it's neither expired nor expiring soon. It vanishes from the banner, the red/orange coloring, and CloseoutDocumentNudge on its last valid day. There are no tests for either predicate.

Why now: it's today's morning-brief idea #3, and verifying it turned up a correctness bug the brief didn't mention. Procore's Sept 15 Project Archive work and Autodesk's handover exports treat expiring closeout paperwork as the item to chase. A banner that goes quiet on the deadline day is the opposite. The fix is small, local, and needs no D1 change.

Sketch: In the model, change isExpiringSoon to !isExpired && days != null && days <= 30 (days >= 0), or compare on calendar dates. Wrap the banner in InkWell + Semantics(button: true, label: '… tap to show') and toggle a local _expiryFilter that pre-filters in _applySearch, then compose it with the search query. Show a "Showing expiring only — Clear" chip. Tests: expires in 10 h (soon, not expired), exactly 30 d, 31 d, 1 min ago (expired), null date, and the widget filter/clear round-trip.

2. One Clear VoiceOver Action per Project Card Buildr+ — UI/Code Health (accessibility)

ProjectCard (lib/widgets/project_card.dart:31) is a bare GestureDetector around a thumbnail, status pill, number, name, address, contract value, six _miniChip icons (12 pt RFI, CO, submittal, meeting, transmittal, and lien icons), and a chevron. It has no Semantics node, so VoiceOver or TalkBack walks each fragment separately and never announces a button or an "open" action. The long-press menu (delete and edit) isn't exposed as a custom action at all. The six chips are the same on every card whatever the project contains, so they're pure decoration that screen readers still visit.

Why now: it's today's morning-brief idea #1, and ProjectCard has 0 tests. This is the most-visited widget in the app, so a semantics test plus a golden-free widget test leaves it better covered. It also fits the 50/50 standard's accessibility bucket, and it's a small, contained change for whoever next touches projects_list_page.dart (Dominique's held task edits the same page).

Sketch: Wrap the card in MergeSemantics/Semantics(button: true, label: '${project.name}, ${status}, ${contract value}', hint: 'Open project', customSemanticsActions: {'Project options': onLongPress}). Wrap the mini-chip row and chevron in ExcludeSemantics. Swap GestureDetector for InkWell so it gets a ripple and focus highlight on web and desktop. Tests: the tester.getSemantics label contains the name and status, there is exactly one tap action, the custom action fires onLongPress, and the layout doesn't overflow at textScaler 2.0.

3. Build the Reports List Lazily DailyReportr+ — UI/Code Health (performance)

ReportsPage (lib/pages/reports/reports_page.dart:101) puts the overview card, admin filter, and report list inside a SingleChildScrollView → Column. The list is a ResponsiveDesktopGrid (:187, :369) whose narrow-screen path is another plain Column of every card, and each one is wrapped in a Dismissible. A superintendent with 18 months of dailies pays the build and layout cost for hundreds of cards before seeing the first one. Admins pay it for every user's reports. Twenty other page call sites in the app already use ListView.builder or SliverList, so this page is the odd one out.

Why now: it's today's morning-brief idea #2. It touches the same file as Katey's held admin-failure task and Sept 29's shared ReportSummaryCard extraction. Doing all three as one reports_page.dart rework means one review and one set of tests instead of three conflicting diffs.

Sketch: Replace the scroll view with CustomScrollView: a SliverToBoxAdapter for the overview and filter, then SliverList.builder on narrow screens. Keep ResponsiveDesktopGrid (or a SliverGrid with a fixed cross-axis count of 2) for wide web so the desktop layout is unchanged. Keep RefreshIndicator and Dismissible keys stable. Tests: with a 500-report fixture, fewer than 30 card builds on first frame (count via a builder hook), scrolling to the last report works, and pull-to-refresh and delete still work.

UI Inconsistencies

Competitive Intel

Tomorrow’s Build Suggestion

This is the twenty-second night without builders, and the assignments are held for the third day. Rotating them doesn't bring the builders back, and both still top the queue. Owner actions before either build: check the 00:15 and 01:15 builder launch jobs (the morning pipeline confirms only the reviewer was retired), 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), plus today's ProjectCard semantics (Recommendation #2) if time remains, since both edit projects_list_page.dart. 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: a single reports_page.dart rework: first Admin Reports That Admit Failure (the retry loop), then the shared ReportSummaryCard without the fake "1 activity" badge, then the lazy SliverList (Recommendation #3). Rationale: it's the stalest app (16 days on main), and all three fixes target the same file. Closr+ expiry banner + boundary fix (Recommendation #1) and Closr+ partial-sync honesty are queued next for whichever builder frees up first.