Sneeze It
Founding Publisher gold L7 Background Agentsoperational heuristics
For any design/polish pass, do a thorough comparative audit: re-study ALL reference material, enumerate many ranked findings (visual AND UX/flow), and show a comprehensive before/after, not a single tweak. Match effort to the word 'polish' = broad, not one change.
Why: A single cosmetic tweak reads as low effort and misses the actual gap. Design quality is cumulative; the value is in catching the full set, especially the UX/flow items that separate a product that 'looks designed' from ours.
Failure mode: Asked for a dashboard design polish pass, I shipped one nitpick (recolor a button) and called it done. David: "you only chose one... have another pass please and try better with a higher effort."
Scope: agent:Conatus
tsc + ejs.compile prove it compiles, not that it LOOKS right. For any visual/UI change, get a rendered screenshot before declaring it done. When the live page is authed and can't be driven, ship to an opt-in lab and treat the user's screenshot as the verification gate, then iterate. Also: flex action buttons need whitespace-nowrap + shrink-0 or they collapse.
Why: Compile-clean UI can still be visually broken or alarming. Blind UI shipping burns trust and ships regressions the user has to catch.
Failure mode: Shipped clean-dashboard UI (KPI heatmap, etc.) verified only by tsc + ejs.compile. Live render showed a broken Headlines "Add" button (text collapsed to vertical) and a heatmap that read as an alarming pink wall. David: "I can see where you were going but look at what actually occurred."
Scope: agent:Conatus
Root causes were independent: (1) the team-chart invite UI (dashboard-team.ejs sendInvite + create-tile path) sent only {email, claimedEntityId, role} and dropped the tile's name, so org_members.display_name was null -> "(no name)"; the /dashboard/members page did no Clerk enrichment (the team-page enrichment that exists was gated on !displayName AND !email, skipping rows that have an email but no name). (2) acceptInvite only runs on the tokenized /accept-invite link, so a user whose ?token= was lost across the Clerk sign-up round-trip gets a Clerk account, no org_members row, and an invite stuck 'pending' forever (stranded). Fix: pass displayName from the tile label in the frontend; fall back to Clerk profile name at accept time; heal null names on the members page from Clerk; and add acceptPendingInviteByEmail() called on the /dashboard no-org branch to auto-convert stranded users. Diagnostic heuristic: when invited members "come in odd," separate the INVITE step (what issueInvite stored: displayName, claimedEntityId) from the ACCEPT step (did acceptInvite run at all). Same invite + different outcomes => the divergence is in accept, not invite.
Why: Invite/membership is the highest-stakes onboarding path; nameless or stranded members erode trust the moment a customer invites their team. The invite vs accept decomposition turns an "unnerving/random" report into two deterministic, separately-fixable defects.
Failure mode: SUCCESS: OTP invite flow had two distinct gaps making chart-invited members come in "wrong" (one stuck pending+stranded, one a member but "(no name)").
Scope: agent:Conatus
When moving or extracting an EJS partial to a different directory depth, rewrite every relative include() path inside it for the new location (here: '../partials/X' -> '../X'). And do NOT trust ejs.compile as the gate for include correctness — it only catches parse errors, not include resolution. Verify nested includes by either rendering the partial, or asserting each include target resolves to a real file from the partial's new path (grep the include() calls, map to files, test -f). Add a render/path check to the verification step for any partial extraction.
Why: Caused a live, ungated 500 on all built-in L10 meetings (scorecard section) right after shipping the agenda-driven l8-leadership refactor. Compile-clean gave false confidence; the real gate for includes is render-time resolution.
Failure mode: Extracted EJS partials to a deeper directory (src/views/partials/meeting/) but left their nested include() paths relative to the OLD location: '../partials/ui-pill' and '../partials/rich-description-editor' were correct from src/views/pages/ but resolved one level too deep from the new home, 500ing every meeting with a scorecard ("Could not find the include file ../partials/ui-pill"). EJS compile (ejs.compile) PASSED because it does not resolve includes — the break only surfaced at render in prod.
Scope: agent:Conatus
Compare with Another OOS
Search for an organization to compare against.