Skip to content

Commit 4fda198

Browse files
Valentin Grünerclaude
andcommitted
fix(webapp): follow the server layer's vocabulary after the restack
Rebasing this layer onto the current server branch brought three collisions that the compiler, not the merge, surfaced. A feedback source is now identified by artifactKind carrying the wire value ("scm.pull_request") rather than by a source enum, so the presentation map is keyed by those values and is no longer typed against a closed union — which is what its own comment already promised for a source the webapp does not know yet. A reflection item now states its outcome, so the fixtures say which of the four they are instead of leaving it to severity. A practice standing can now be NOT_OBSERVED or NO_OPPORTUNITY. The local UNMEASURED marker stood in for exactly that gap while those were area-only facts; they are rendered from the practice's own evidence now, and UNMEASURED is left for the one case the payload still cannot answer — a practice missing from the standings map altogether. AreaPill, which main extracted while this layer moved area-visuals to shared, follows the module to its new home; specific may depend on shared, not the other way round. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
1 parent 7d3b736 commit 4fda198

10 files changed

Lines changed: 109 additions & 38 deletions

webapp/src/components/admin/practice-catalog/AreaPill.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cva, type VariantProps } from "class-variance-authority";
22
import { CircleDashed } from "lucide-react";
3-
import { getAreaVisual } from "@/components/admin/practice-catalog/area-visuals";
3+
import { getAreaVisual } from "@/components/shared/area-visuals";
44
import { cn } from "@/lib/utils";
55

66
const areaPillVariants = cva("flex shrink-0 items-center justify-center", {

webapp/src/components/admin/practice-reviews/ReviewPracticeLink.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Link } from "@tanstack/react-router";
22
import type { Practice, ReviewPracticeArea } from "@/api/types.gen";
33
import { AreaPill } from "@/components/admin/practice-catalog/AreaPill";
44
import { PracticeDetailHoverCard } from "@/components/admin/practice-catalog/PracticeDetailHoverCard";
5-
import { getAreaVisual } from "@/components/shared/area-visuals";
65
import { cn } from "@/lib/utils";
76

87
export interface ReviewPracticeLinkProps {

webapp/src/components/profile/PracticeAreaDetailPage.stories.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ const mixedStatus: PracticeAreaStatus = {
4848
feedbackSpanDays: 34,
4949
feedbackSince: new Date("2026-06-24T09:00:00Z"),
5050
sources: [
51-
{ source: "PULL_REQUEST", count: 12 },
52-
{ source: "ISSUE", count: 3 },
51+
{ artifactKind: "scm.pull_request", count: 12 },
52+
{ artifactKind: "scm.issue", count: 3 },
5353
],
5454
items: [],
5555
};
@@ -123,7 +123,7 @@ const activity: ObservationList[] = [
123123
id: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c001",
124124
practiceSlug: "scope-one-concern",
125125
practiceName: "Scope the change to one concern",
126-
artifactKind: "PULL_REQUEST",
126+
artifactKind: "scm.pull_request",
127127
artifactId: 41,
128128
summary: "Change bundles two unrelated concerns",
129129
presence: "ABSENT",
@@ -137,7 +137,7 @@ const activity: ObservationList[] = [
137137
id: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c002",
138138
practiceSlug: "describe-what-changed",
139139
practiceName: "Describe what changed and why",
140-
artifactKind: "PULL_REQUEST",
140+
artifactKind: "scm.pull_request",
141141
artifactId: 39,
142142
summary: "PR description explains the why",
143143
presence: "PRESENT",
@@ -319,7 +319,7 @@ export const ObservationExpanded: Story = {
319319
id: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c001",
320320
practiceSlug: "scope-one-concern",
321321
practiceName: "Scope the change to one concern",
322-
artifactKind: "PULL_REQUEST",
322+
artifactKind: "scm.pull_request",
323323
artifactId: 41,
324324
summary: "Change bundles two unrelated concerns",
325325
presence: "ABSENT",

webapp/src/components/profile/PracticeAreaDetailPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const status: PracticeAreaStatus = {
2525
areaSlug: area.slug,
2626
areaName: area.name,
2727
status: "DEVELOPING",
28-
sources: [{ source: "PULL_REQUEST", count: 2 }],
28+
sources: [{ artifactKind: "scm.pull_request", count: 2 }],
2929
guidance:
3030
"Your recent feedback points to “Scope the change to one concern” as the next practice to focus on.",
3131
guidanceSource: "RULE_BASED",

webapp/src/components/profile/PracticeAreaDetailPage.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ import { SEVERITY_ORDER, SEVERITY_PRESENTATION, type SeverityKey } from "./sever
5252
/**
5353
* A single practice's standing in the selection list.
5454
*
55-
* <p>Keyed by the PRACTICE standing vocabulary plus one local absent marker — deliberately NOT by the area
56-
* status. The reflection surface only ever reports the three verdicts for a practice; the area's no-verdict
57-
* reasons (`NOT_OBSERVED`/`NO_OPPORTUNITY`) are aggregate facts and cannot be attributed to
58-
* an individual practice from this payload. Sharing one map made a practice inherit area-level wording it
59-
* has no evidence for.
55+
* <p>Keyed by the PRACTICE standing vocabulary plus one local absent marker. The no-verdict reasons used to
56+
* be area-only facts that could not be attributed to a single practice, which is what the local marker stood
57+
* in for; the reflection payload now carries them per practice, so they are rendered from the practice's own
58+
* evidence rather than inherited from its area. `UNMEASURED` remains for the one case the payload still
59+
* cannot answer: a practice absent from the standings map entirely.
6060
*/
6161
type PracticeStandingKey = NonNullable<ReflectionPractice["standing"]> | "UNMEASURED";
6262

@@ -82,6 +82,18 @@ const STANDING_NODE: Record<
8282
textClass: "text-success",
8383
label: "Going well",
8484
},
85+
NOT_OBSERVED: {
86+
Icon: CircleDashedIcon,
87+
circleClass: "border-border text-muted-foreground",
88+
textClass: "text-muted-foreground",
89+
label: "Not observed yet",
90+
},
91+
NO_OPPORTUNITY: {
92+
Icon: CircleDashedIcon,
93+
circleClass: "border-border text-muted-foreground",
94+
textClass: "text-muted-foreground",
95+
label: "No occasion yet",
96+
},
8597
UNMEASURED: {
8698
Icon: CircleDashedIcon,
8799
circleClass: "border-border text-muted-foreground",
@@ -309,7 +321,10 @@ export function PracticeAreaDetailPage({
309321
if (standing === "STRENGTH" && practice.whatGoodLooksLike) {
310322
return `Keep doing this: ${practice.whatGoodLooksLike}`;
311323
}
312-
if (standing === "UNMEASURED") {
324+
if (standing === "NO_OPPORTUNITY") {
325+
return "Nothing to act on yet — the reviews ran and your work offered no occasion for this practice.";
326+
}
327+
if (standing === "NOT_OBSERVED" || standing === "UNMEASURED") {
313328
return "No focused next step yet. It will appear after this practice is observed in reviewed work.";
314329
}
315330
return practice.whatGoodLooksLike;

webapp/src/components/profile/PracticeAreaStatusCard.stories.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,18 @@ const statuses: Record<string, PracticeAreaStatus> = {
7272
{
7373
observationId: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c001",
7474
title: "Change bundles two unrelated concerns",
75+
outcome: "COMMISSION_PROBLEM",
7576
deliveredFeedback:
7677
"Split the refactoring from the feature change so each can be reviewed alone.",
7778
severity: "MINOR",
78-
artifactKind: "PULL_REQUEST",
79+
artifactKind: "scm.pull_request",
7980
artifactId: 41,
8081
origin: "LIVE",
8182
},
8283
],
8384
sources: [
84-
{ source: "PULL_REQUEST", count: 12 },
85-
{ source: "ISSUE", count: 3 },
85+
{ artifactKind: "scm.pull_request", count: 12 },
86+
{ artifactKind: "scm.issue", count: 3 },
8687
],
8788
},
8889
"acting-on-review-feedback": {
@@ -98,13 +99,14 @@ const statuses: Record<string, PracticeAreaStatus> = {
9899
{
99100
observationId: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c002",
100101
title: "Every review thread answered",
102+
outcome: "DEMONSTRATED_STRENGTH",
101103
deliveredFeedback: "You consistently close the loop on review threads — keep it up.",
102-
artifactKind: "PULL_REQUEST",
104+
artifactKind: "scm.pull_request",
103105
artifactId: 42,
104106
origin: "LIVE",
105107
},
106108
],
107-
sources: [{ source: "PULL_REQUEST", count: 9 }],
109+
sources: [{ artifactKind: "scm.pull_request", count: 9 }],
108110
},
109111
"testing-discipline": {
110112
areaSlug: "testing-discipline",
@@ -121,16 +123,17 @@ const statuses: Record<string, PracticeAreaStatus> = {
121123
{
122124
observationId: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c003",
123125
title: "Behaviour change shipped without a test",
126+
outcome: "OMISSION_GAP",
124127
deliveredFeedback: "Add a test that fails without this fix so the behaviour stays covered.",
125128
severity: "MAJOR",
126-
artifactKind: "PULL_REQUEST",
129+
artifactKind: "scm.pull_request",
127130
artifactId: 43,
128131
origin: "LIVE",
129132
},
130133
],
131134
sources: [
132-
{ source: "PULL_REQUEST", count: 4 },
133-
{ source: "ISSUE", count: 1 },
135+
{ artifactKind: "scm.pull_request", count: 4 },
136+
{ artifactKind: "scm.issue", count: 1 },
134137
],
135138
},
136139
};
@@ -194,7 +197,7 @@ const moreStatuses: Record<string, PracticeAreaStatus> = {
194197
feedbackSpanDays: 27,
195198
feedbackSince: new Date("2026-07-01T09:00:00Z"),
196199
items: [],
197-
sources: [{ source: "PULL_REQUEST", count: 6 }],
200+
sources: [{ artifactKind: "scm.pull_request", count: 6 }],
198201
},
199202
"code-craftsmanship": {
200203
areaSlug: "code-craftsmanship",
@@ -208,7 +211,7 @@ const moreStatuses: Record<string, PracticeAreaStatus> = {
208211
feedbackSpanDays: 19,
209212
feedbackSince: new Date("2026-07-09T09:00:00Z"),
210213
items: [],
211-
sources: [{ source: "PULL_REQUEST", count: 5 }],
214+
sources: [{ artifactKind: "scm.pull_request", count: 5 }],
212215
},
213216
// No feedback at all: the card falls back to its badge and draws no ring.
214217
communication: {
@@ -297,7 +300,7 @@ export const WithSlackFeedback: Story = {
297300
...statuses["review-ready-work"],
298301
sources: [
299302
...statuses["review-ready-work"].sources,
300-
{ source: "CONVERSATION_THREAD", count: 5 },
303+
{ artifactKind: "chat.conversation_thread", count: 5 },
301304
],
302305
},
303306
},

webapp/src/components/profile/PracticeAreaStatusCard.test.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,16 @@ const statuses: Record<string, PracticeAreaStatus | undefined> = {
6565
title: "Missing rollout plan",
6666
deliveredFeedback: "Add a rollout section describing how the change ships.",
6767
severity: "MAJOR",
68-
artifactKind: "PULL_REQUEST",
68+
outcome: "OMISSION_GAP",
69+
artifactKind: "scm.pull_request",
6970
artifactId: 42,
7071
origin: "LIVE",
7172
},
7273
],
7374
sources: [
74-
{ source: "PULL_REQUEST", count: 3 },
75-
{ source: "ISSUE", count: 1 },
76-
{ source: "CONVERSATION_THREAD", count: 2 },
75+
{ artifactKind: "scm.pull_request", count: 3 },
76+
{ artifactKind: "scm.issue", count: 1 },
77+
{ artifactKind: "chat.conversation_thread", count: 2 },
7778
],
7879
},
7980
collaboration: {
@@ -91,12 +92,13 @@ const statuses: Record<string, PracticeAreaStatus | undefined> = {
9192
{
9293
observationId: "0b54c9f2-8f4e-4a53-9be1-0e6a35a1c002",
9394
title: "Responsive to review feedback",
94-
artifactKind: "PULL_REQUEST",
95+
outcome: "DEMONSTRATED_STRENGTH",
96+
artifactKind: "scm.pull_request",
9597
artifactId: 43,
9698
origin: "LIVE",
9799
},
98100
],
99-
sources: [{ source: "PULL_REQUEST", count: 1 }],
101+
sources: [{ artifactKind: "scm.pull_request", count: 1 }],
100102
},
101103
"review-communication": {
102104
areaSlug: "review-communication",

webapp/src/components/profile/PracticeAreaStatusCard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function PracticeAreaStatusCard({
155155
areas
156156
.flatMap((area) => statuses[area.slug]?.sources ?? [])
157157
.filter((sourceCount) => sourceCount.count > 0)
158-
.map((sourceCount) => sourceCount.source),
158+
.map((sourceCount) => sourceCount.artifactKind),
159159
);
160160
const sourceNouns = Object.entries(PRACTICE_AREA_SOURCE_META)
161161
.filter(([source]) => presentSources.has(source))
@@ -218,7 +218,7 @@ export function PracticeAreaStatusCard({
218218
const sources = (status?.sources ?? [])
219219
.map((sourceCount) => ({
220220
sourceCount,
221-
meta: PRACTICE_AREA_SOURCE_META[sourceCount.source],
221+
meta: PRACTICE_AREA_SOURCE_META[sourceCount.artifactKind],
222222
}))
223223
.filter((entry) => entry.meta !== undefined);
224224
const totalSourceCount = sources.reduce(
@@ -332,7 +332,7 @@ export function PracticeAreaStatusCard({
332332
if (!meta) return null;
333333
const noun = sourceCount.count === 1 ? meta.singular : meta.plural;
334334
return (
335-
<Tooltip key={sourceCount.source}>
335+
<Tooltip key={sourceCount.artifactKind}>
336336
<TooltipTrigger
337337
render={
338338
<button

webapp/src/components/profile/practice-area-status-presentation.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,17 @@ export const PRACTICE_AREA_STATUS_BADGE: Record<
7777
*/
7878
export const PRACTICE_AREA_SOURCE_META: Partial<
7979
Record<
80-
FeedbackSourceCount["source"],
80+
FeedbackSourceCount["artifactKind"],
8181
{ Icon: OcticonComponent | BrandIcon; singular: string; plural: string }
8282
>
8383
> = {
84-
PULL_REQUEST: { Icon: GitPullRequestIcon, singular: "pull request", plural: "pull requests" },
85-
ISSUE: { Icon: IssueOpenedIcon, singular: "issue", plural: "issues" },
86-
CONVERSATION_THREAD: {
84+
"scm.pull_request": {
85+
Icon: GitPullRequestIcon,
86+
singular: "pull request",
87+
plural: "pull requests",
88+
},
89+
"scm.issue": { Icon: IssueOpenedIcon, singular: "issue", plural: "issues" },
90+
"chat.conversation_thread": {
8791
Icon: SlackIcon,
8892
singular: "Slack conversation",
8993
plural: "Slack conversations",

0 commit comments

Comments
 (0)