Fix twenty findings from the Motion review
CI / verify (push) Successful in 4m47s
CI / publish (push) Failing after 3s

Each was raised by a reviewer and then survived an independent attempt to
refute it. The four that mattered most:

- A third of the starter library was invisible. Three templates authored
  `fields` shapes no renderer read — decisions, blockingSet, checks,
  steps and the rest — so about forty records rendered as no DOM at all,
  in the library and again on the engagement that instantiated them.
  Nothing failed: a renderer returns null for a key set it does not
  recognise, and a header-plus-body page looks like a template written
  that way. FieldsView now reads every key the seeds carry.
- "Add a framework" opened a picker that could never match, because the
  dialog was seeded with both the forced kind and the deal's stage, and
  qualification serves only the qualification stage. The stage is now
  dropped when MOTION_KIND_STAGES says the pair is incoherent.
- Piggy reported the promotion count as an exact figure capped at 8,
  against a tile showing the true count beside it. It is now counted in
  SQL, and all three motion tools carry a ResultScope whose denominator
  is shared lineages — never rows, never private drafts.
- No Motion test went through createApp, so the whole feature could be
  unmounted with a green suite. That is the AGENTS.md §5 trap that
  already cost this project read-guards.ts and learn.ts.

Also: both sides of the instantiate/edit race now lock, so a template
cannot be rewritten under an artefact that has copied it; concurrent
engagement opens queue on the deal row and get the 409 the handler
already promised rather than a 500; latestScore uses DISTINCT ON instead
of losing engagements past a 200-row cap; the migration adds the
scored_by_user_id foreign key the schema declares; and the demo clear
refunds usage_count for engagements it reaches by cascade, which
otherwise left starter templates permanently un-editable.

Verified on a fresh database: 16 migrations apply and re-apply as a
no-op, both seeds idempotent, usage_count back to zero after --clear.
564 unit tests pass. Every Motion route measures zero horizontal
overflow at 393 and 1440 in both themes, and all twelve seeded field
trees are asserted onto the screen by scripts/motion-fields-check.mjs.

One thing left open deliberately: the shipped qualification scorecard's
five bands and MOTION_BANDS' four are calibrated differently. The
framework's table is now titled as its own guidance rather than the
product's verdict, which removes the contradiction on screen. Making the
framework's calibration authoritative over the persisted band column is
a product decision nobody has made.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 19:08:55 -07:00
parent 376ef3d597
commit 15c72ade1c
16 changed files with 1281 additions and 66 deletions
+132 -21
View File
@@ -62,6 +62,7 @@ import { CalendarService } from '@pig/api/src/services/calendar';
import {
and,
count,
countDistinct,
desc,
eq,
gte,
@@ -210,6 +211,16 @@ const SUPPLY_DEALS_LABEL = 'supply deal(s) on the book';
const ACCOUNTS_LABEL = 'account(s) on the book';
const CONTACTS_LABEL = 'contact(s) in the CRM';
/**
* The denominators the motion tools are drawn from.
*
* The library one says `shared` and says `lineage` because both are real
* restrictions on the figure: private drafts are outside it by design, and a
* lineage is one piece of practice however many versions it has carried.
*/
const MOTION_LIBRARY_LABEL = 'shared template lineage(s) in the motion library';
const ENGAGEMENTS_LABEL = 'engagement(s) on the book';
/** One whole-table count, for use as a denominator. */
function rowCount(rows: readonly { value: number }[]): number {
return rows[0]?.value ?? 0;
@@ -1086,7 +1097,12 @@ function countBy<Row>(rows: readonly Row[], key: (row: Row) => string): Record<s
* closed stages are excluded throughout — a won deal has left the motion.
*/
async function readMotionSummary(db: Database): Promise<unknown> {
const [libraryRead, engagementRead, promotions] = await Promise.all([
// One predicate for the exemplars and for the count below them, so the list
// and the figure cannot come to describe different sets. It is also the
// predicate the /motion tile counts: `motionLibraryWhere({})` already excludes
// archived rows, and promotion always writes `visibility: 'shared'`.
const promoted = and(motionLibraryWhere({}), isNotNull(motionTemplates.originArtifactId))!;
const [libraryRead, engagementRead, promotions, promotionTotal] = await Promise.all([
db
.select({
slug: motionTemplates.slug,
@@ -1114,13 +1130,21 @@ async function readMotionSummary(db: Database): Promise<unknown> {
createdAt: motionTemplates.createdAt,
})
.from(motionTemplates)
.where(and(motionLibraryWhere({}), isNotNull(motionTemplates.originArtifactId)))
.where(promoted)
.orderBy(desc(motionTemplates.createdAt))
.limit(EXEMPLARS),
// Counted in SQL rather than read off the list above it, which is capped at
// EXEMPLARS. Promotions is the one figure whose job is to show the loop
// closing, so it is the one figure that must not stop moving: read off the
// list it would say 8 the moment the loop started working, for ever, beside
// a tile counting 12 exactly. Versions rather than lineages, like the tile —
// a second promotion into one lineage is a second time the loop closed.
db.select({ value: count() }).from(motionTemplates).where(promoted),
]);
const { rows: templateRows, truncated: libraryTruncated } = bounded(libraryRead);
const { rows: openEngagements, truncated: engagementsTruncated } = bounded(engagementRead);
const truncated = libraryTruncated || engagementsTruncated;
const promotionCount = rowCount(promotionTotal);
const lineages = newestPerSlug(templateRows);
const engagementsByStage = countBy(openEngagements, (row) => row.stage);
@@ -1133,21 +1157,55 @@ async function readMotionSummary(db: Database): Promise<unknown> {
`${atLeast(lineages.length, libraryTruncated)} shared template(s) across ` +
`${Object.keys(countBy(lineages, (row) => row.kind)).length} of ${MOTION_KINDS.length} ` +
`kind(s), and ${atLeast(openEngagements.length, engagementsTruncated)} open engagement(s). ` +
(uncovered.length === 0
? 'Every live demand stage has at least one shared template.'
: `No shared template covers ${uncovered.join(', ')}.`) +
` ${promotions.length} artifact(s) promoted back into the library recently.` +
(libraryTruncated
? 'Which stages the library misses cannot be told from this scan, because it hit its ' +
'row cap. '
: uncovered.length === 0
? 'Every live demand stage has at least one shared template. '
: `No shared template covers ${uncovered.join(', ')}. `) +
`${promotionCount} artifact(s) promoted back into the library in all` +
(promotions.length ? `, newest ${promotions.length} listed.` : '.') +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
/** Unfiltered within the shared library, so this read IS its own denominator. */
scope: resultScope({
covers: 'are shared and not archived',
matched: lineages.length,
total: lineages.length,
totalLabel: MOTION_LIBRARY_LABEL,
listed: 0,
truncated: libraryTruncated,
}),
truncated,
sharedTemplates: lineages.length,
openEngagements: openEngagements.length,
uncoveredStages: uncovered,
/**
* Null rather than a list when the scan was capped. "No shared template
* covers procurement" is a definite negative, and a definite negative drawn
* from part of the library is a claim the read cannot support — the stage
* may well be covered by a lineage beyond the cap. An exact answer under
* truncation needs a per-slug newest-version aggregate in SQL, which is a
* larger change than this figure is worth.
*/
uncoveredStages: libraryTruncated ? null : uncovered,
stages: DEMAND_OPEN_STAGES.map((stage) => ({
stage,
openEngagements: engagementsByStage[stage] ?? 0,
sharedTemplates: lineages.filter((template) => template.stage === stage).length,
})),
libraryByKind: countBy(lineages, (row) => row.kind),
promotions: promotionCount,
/**
* The exact figure sits beside the list on purpose. A model handed eight
* rows and no total reads the length of the list as the count, which is the
* mistake `ResultScope` exists for.
*/
recentPromotionsScope: resultScope({
covers: 'were promoted back into the library from an engagement artefact',
matched: promotionCount,
total: promotionCount,
totalLabel: 'promoted template version(s) in the motion library',
listed: promotions.length,
}),
// The loop made visible: what the last few engagements gave back.
recentPromotions: promotions.map((row) => ({
title: row.title,
@@ -1159,8 +1217,8 @@ async function readMotionSummary(db: Database): Promise<unknown> {
}
async function readMotionLibrary(db: Database, filter: MotionLibraryFilter): Promise<unknown> {
const { rows, truncated } = bounded(
await db
const [libraryRead, libraryTotal] = await Promise.all([
db
.select({
slug: motionTemplates.slug,
kind: motionTemplates.kind,
@@ -1174,25 +1232,60 @@ async function readMotionLibrary(db: Database, filter: MotionLibraryFilter): Pro
.where(motionLibraryWhere(filter))
.orderBy(desc(motionTemplates.updatedAt))
.limit(SCAN_LIMIT + 1),
);
/**
* The denominator, and both halves of it are decisions.
*
* `motionLibraryWhere({})` rather than a bare `count()` over the table: a
* total that included private rows would publish the existence and the size
* of colleagues' drafts through the back door, which is the one thing the
* library rule exists to withhold — a denominator leaks as readily as a
* list. And `countDistinct(slug)` rather than `count()`, because the slug is
* the identity of a lineage: counting versions reports a library several
* times the size of the one anybody can choose from, which is the same
* mistake `newestPerSlug` exists to avoid on the matched side. Counted in
* SQL, so the total stays exact when the read beside it hits its cap.
*/
db
.select({ value: countDistinct(motionTemplates.slug) })
.from(motionTemplates)
.where(motionLibraryWhere({})),
]);
const { rows, truncated } = bounded(libraryRead);
const lineages = newestPerSlug(rows as LineageRow[]);
const sharedTemplates = rowCount(libraryTotal);
const described = [
filter.kind ? `kind ${filter.kind}` : null,
filter.stage ? `stage ${filter.stage}` : null,
filter.query ? `"${filter.query}"` : null,
].filter((part): part is string => part !== null);
const scope = described.length ? ` matching ${described.join(', ')}` : '';
const matching = described.length ? ` matching ${described.join(', ')}` : '';
return {
headline:
(lineages.length === 0
? `No shared template${scope}. Private drafts are not searched, so a template may ` +
'exist and not be visible here.'
: `${atLeast(lineages.length, truncated)} shared template(s)${scope}, newest version ` +
'of each.') + (truncated ? ` ${TRUNCATION_NOTE}` : ''),
? `None of the ${sharedTemplates} ${MOTION_LIBRARY_LABEL}${matching}. Private drafts are ` +
'not searched, so a template may exist and not be visible here.'
: `${atLeast(lineages.length, truncated)} of ${sharedTemplates} ` +
`${MOTION_LIBRARY_LABEL}${matching}, newest version of each.`) +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
scope: resultScope({
covers: described.length ? `match ${described.join(', ')}` : 'are shared and not archived',
matched: lineages.length,
total: sharedTemplates,
totalLabel: MOTION_LIBRARY_LABEL,
listed: Math.min(lineages.length, EXEMPLARS),
filters: {
...(filter.kind ? { kind: filter.kind } : {}),
...(filter.stage ? { stage: filter.stage } : {}),
...(filter.query ? { query: filter.query } : {}),
},
truncated,
}),
truncated,
count: lineages.length,
/** The denominator as a bare field: this is how big the shared library is. */
sharedTemplates,
byKind: countBy(lineages, (row) => row.kind),
// Most recently updated first: the practice people are actually amending.
templates: lineages.slice(0, EXEMPLARS).map((template) => ({
@@ -1220,8 +1313,8 @@ async function readMotionLibrary(db: Database, filter: MotionLibraryFilter): Pro
*/
async function readEngagements(db: Database, query: string | null): Promise<unknown> {
const fragment = query ? likeFragment(query) : null;
const { rows, truncated } = bounded(
await db
const [engagementRead, engagementTotal] = await Promise.all([
db
.select({
id: engagements.id,
status: engagements.status,
@@ -1241,7 +1334,13 @@ async function readEngagements(db: Database, query: string | null): Promise<unkn
)
.orderBy(desc(engagements.openedAt))
.limit(SCAN_LIMIT + 1),
);
// Every engagement, open or closed and whatever the query. Without it a
// search that matches two is the only figure in the payload, and "we have
// two engagements" is the answer that comes back.
db.select({ value: count() }).from(engagements),
]);
const { rows, truncated } = bounded(engagementRead);
const totalEngagements = rowCount(engagementTotal);
const exemplars = rows.slice(0, EXEMPLARS);
const ids = exemplars.map((row) => row.id);
@@ -1280,13 +1379,25 @@ async function readEngagements(db: Database, query: string | null): Promise<unkn
headline:
(rows.length === 0
? query
? `No engagement matches "${query}".`
? `None of the ${totalEngagements} ${ENGAGEMENTS_LABEL} match "${query}".`
: 'No demand deal has an engagement running against it yet.'
: `${atLeast(rows.length, truncated)} engagement(s)${query ? ` matching "${query}"` : ''}, ` +
`of which ${rows.filter((row) => row.status === 'open').length} open.`) +
: `${atLeast(rows.length, truncated)} of ${totalEngagements} ${ENGAGEMENTS_LABEL}` +
`${query ? ` match "${query}"` : ''}, of which ` +
`${rows.filter((row) => row.status === 'open').length} open.`) +
(truncated ? ` ${TRUNCATION_NOTE}` : ''),
scope: resultScope({
covers: query ? `match "${query}"` : 'are on the book',
matched: rows.length,
total: totalEngagements,
totalLabel: ENGAGEMENTS_LABEL,
listed: exemplars.length,
filters: query ? { query } : {},
truncated,
}),
truncated,
count: rows.length,
/** The denominator as a bare field: engagements exist that this did not match. */
totalEngagements,
byStatus: countBy(rows, (row) => row.status),
byStage: countBy(rows, (row) => row.stage),
engagements: exemplars.map((row) => {