From e9f587c3631a8b756540a69bb400b97bed45bad1 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 00:33:25 -0700 Subject: [PATCH] =?UTF-8?q?docs(workflow-learnings):=20correct=20the=20"bo?= =?UTF-8?q?unded"=20heuristic=20=E2=80=94=20a=20clock-shaped=20dep=20is=20?= =?UTF-8?q?not=20a=20fast=20one=20(#3012)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The severity heuristic I wrote in #2998 sorted dependencies **by name**, and #3007 is the counterexample. ## What I got wrong I classified `lifecycleDates` as *bounded* because its dep list contains `lifecycleNowMs`, and deferred it in #3001 with the line *"any wrong answer there survives only until the next update."* That value is driven by a **local-midnight boundary timer** — one tick per card per day. So a finished card shows no completion date for up to **twenty-four hours**. @gsxdsm found it after I'd written it off. `nowMs`, `Ticker` and `lastFetchTimeMs` span a live 30-second ticker, a per-fetch stamp, and a daily boundary. Sorting them by name puts a day-long defect in the same bucket as a 30-second one. ## The sharper half A card in a **completion lane doesn't subscribe to the shared live ticker at all** — that's exactly what the ticker's eligibility check is for, and what #2996 fixed. So the "fast" dependency that would have rescued this population is the one thing that population never receives. The corrected question is: **which dependencies refresh *for this population*** — not which ones appear in the list. Two of my three severity calls in that sweep leaned on a dep that the affected cards structurally never get. ## Why this is worth a PR rather than a quiet edit The doc is what the next person triages against. #3001 explicitly told them the four "bounded" sites were deprioritised **by design** — on reasoning that was wrong for at least one of them. Leaving that in place means someone defers a day-long defect on my say-so. Docs only. No code, no baselines. Co-authored-by: Claude Opus 5 (1M context) --- ...ifecycle-conversions-that-score-as-wins.md | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/solutions/workflow-learnings/lifecycle-conversions-that-score-as-wins.md b/docs/solutions/workflow-learnings/lifecycle-conversions-that-score-as-wins.md index b31891a4ed..8eb9351ad5 100644 --- a/docs/solutions/workflow-learnings/lifecycle-conversions-that-score-as-wins.md +++ b/docs/solutions/workflow-learnings/lifecycle-conversions-that-score-as-wins.md @@ -293,10 +293,23 @@ Found: the blocker fan-out map (empty trait index, permanent), the card's live e **Two properties decide severity, and both are checkable by reading the dependency list:** -1. **Does any dependency refresh quickly?** A live clock, `allTasks`, a task identity — any of these - rebuilds the closure on the next update, so the wrong answer is a bounded window rather than - permanent. The near-duplicate chip keys on `allTasks` and self-heals on the next task refresh; the - time indicator keys on `task.column`, which never changes, so it never recovers. +1. **Does any dependency refresh quickly?** `allTasks` or a task identity rebuilds the closure on the + next update, so the wrong answer is a bounded window rather than permanent. The near-duplicate + chip keys on `allTasks` and self-heals on the next task refresh; the time indicator keys on + `task.column`, which never changes, so it never recovers. + + **A CLOCK-SHAPED DEPENDENCY IS NOT AUTOMATICALLY A FAST ONE — read its cadence, not its name.** + I classified `lifecycleDates` as bounded on the strength of a `lifecycleNowMs` dependency and + deferred it. That value is driven by a LOCAL-MIDNIGHT boundary timer, one tick per card per day, + so a finished card shows no completion date for up to twenty-four hours. The operator found it + after I had written it off. `nowMs`, `Ticker` and `lastFetchTimeMs` span a live 30-second ticker, + a per-fetch stamp and a daily boundary; sorting them by name puts a day-long defect in the same + bucket as a 30-second one. + + The interaction is worth keeping too: a card in a completion lane does not subscribe to the shared + live ticker at all (that is what the ticker's own eligibility check is for), so the "fast" + dependency that would have rescued it is the one thing it never receives. Ask which dependencies + refresh FOR THIS POPULATION, not which ones exist in the list. 2. **Is the value covered TRANSITIVELY?** A dependency that itself lists the flags gets a new identity when they arrive, which propagates. `TaskCard`'s context-menu memo omits all three role flags and is nonetheless correct, because it depends on `taskActionMenuModel.actions` and that