Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fe73248
feat(webapp): show practice-area standing and its evidence on the pro…
Aug 22, 2026
0137b24
fix(webapp): follow the server layer's vocabulary after the restack
Aug 24, 2026
25975e3
fix(webapp): drop the trend states the server no longer reports
Aug 24, 2026
627958a
refactor(webapp): adopt practice-group profile contracts
FelixTJDietrich Aug 27, 2026
0ea508d
fix(webapp): keep profile evidence keyboard accessible
FelixTJDietrich Aug 27, 2026
6ec9da4
fix(webapp): keep profile query state coherent
FelixTJDietrich Aug 27, 2026
bf1ae49
Merge branch 'main' into feat/practice-area-webapp
Aug 31, 2026
7f320bd
fix(webapp): name the reviewed work's own source
Aug 31, 2026
561253b
refactor(webapp): let one registry name a standing
Aug 31, 2026
615cb13
refactor(webapp): drop what the profile never rendered
Aug 31, 2026
dd016fd
refactor(webapp): take provider copy from the one place that owns it
Aug 31, 2026
fa1d048
refactor(webapp): render practice statuses through the shared registries
Aug 31, 2026
b051306
fix(webapp): quote an object source as what it is
Aug 31, 2026
c24dab8
docs(webapp): stop claiming that "area" survives in code
Aug 31, 2026
0960849
docs(release): add the migration fragment the operator note promised
Aug 31, 2026
b7ae3a4
fix(webapp): say why a quote is missing, and show the sixth outcome
Aug 31, 2026
b1ba1b0
fix(webapp): restore the trend chip's reach and the standing slash
Aug 31, 2026
c9d1717
fix(webapp): only claim what the review actually established
Aug 31, 2026
398cbf6
docs(release): describe the wording this branch actually ships
Aug 31, 2026
12024bd
feat(profile): let a developer answer feedback, not just rate it
Aug 31, 2026
33af36a
refactor(profile): give the trend its registry and its missing stories
Aug 31, 2026
e614581
refactor(profile): let the detail page use the filters the repo alrea…
Aug 31, 2026
34cd818
refactor(profile): join a practice's facts where they are all in hand
Aug 31, 2026
cf0ccaf
feat(profile): drop the review-run filters, name the standings
Aug 31, 2026
13ab1f5
fix(webapp): mark only the side of a change that says something
Aug 31, 2026
f01934e
refactor(profile): gate the page at the route, and story what was unseen
Aug 31, 2026
67f894c
docs(profile): trim the registry comments to what the others carry
Aug 31, 2026
f0c2329
fix(profile): stop promising a link the evidence block does not have
Aug 31, 2026
9f94032
fix(profile): make the group cards headings a reader can navigate by
Aug 31, 2026
2047fff
refactor(profile): hand the profile a section, not a section's props
Aug 31, 2026
a965d4a
fix(profile): show the failure the profile actually hit
Aug 31, 2026
02e41cd
refactor(profile): keep an outcome's icon with its words
Aug 31, 2026
44f43ba
refactor(profile): drop a second import path nobody took
Aug 31, 2026
cc59640
docs(release): let the notes describe what shipped
Aug 31, 2026
1248d54
Merge remote-tracking branch 'origin/main' into feat/practice-area-we…
Aug 31, 2026
a07f687
Merge branch 'main' into feat/practice-area-webapp
FelixTJDietrich Sep 1, 2026
b00dbff
Merge branch 'main' into feat/practice-area-webapp
FelixTJDietrich Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/practice-group-detail-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hephaestus": minor
---

A developer can open a practice group from their profile and see what its standing is actually built from: which practices contribute to the group, where each of them stands, and the feedback behind them. A standing is no longer a label you have to take on trust.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"hephaestus": minor
---

A practice group with no verdict now says which kind of silence it is instead of one catch-all "No feedback yet": whether nothing in it has been reviewed for you yet, or whether it was reviewed and nothing could be judged — because the practices did not apply, or because the evidence did not settle the question. A developer can tell a review that ran and found nothing from one that never ran.

An observation carries the same severity wording wherever it appears, and every severity is told apart by its own icon rather than by colour alone.

The evidence behind an observation reads as a quoted passage with its source named. A quote from code shows the file and the line range, with numbered lines below, and is marked when it comes from the old side of the change — the line quoted is then not what the file says now. A quote from a conversation or a document shows the source and the passage without line numbers — those numbers are positions inside a stored copy, not places you could open, and printing them made a chat message look like a file. When a quote is withheld, the block says why: the secret scanner never stores text that looks like a credential, and the location is still named so you can read it at the source.
7 changes: 7 additions & 0 deletions .changeset/practice-group-review-runs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"hephaestus": minor
---

A practice group now shows its review runs as complete moments: every observation from one review stays together, instead of being split across pages so that a review is only ever half-visible.

Developers can answer the feedback they receive, not just rate it. Alongside marking a piece of feedback helpful or unhelpful, they can record what they did about it — addressed, disputed, or not applicable — and explain it in their own words. Disputing asks for that explanation, so a disagreement always arrives with a reason attached. Any part of an answer can be changed or withdrawn later.
5 changes: 5 additions & 0 deletions .changeset/practice-group-standing-cards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hephaestus": minor
---

On their own profile, a developer now sees a compact summary per practice group: where they currently stand, the guidance behind that standing, and how the group has developed across recently reviewed work. These summaries appear on your own profile only — the standing is derived for the signed-in developer, so another person's profile does not show them.
2 changes: 1 addition & 1 deletion .changeset/practice-standing-feedback-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

The application API can now answer where a developer stands in each practice group: the current standing and its guidance, how the group has developed across recently reviewed work, which kinds of work contributed feedback, a filterable observation history, and the complete review runs behind it. An undecided observation remains visible in that history without being presented as a verdict. Developers can also replace or delete their response to delivered feedback, recording whether it was helpful, how they handled it, and an optional explanation. Every endpoint answers only for the signed-in developer.

**Operators:** direct API callers must replace `/practice-areas` with `/practice-groups`, use the corresponding group schema and field names, replace `/practices/learner` with `/practices/reviewed`, and move from the older reaction endpoint to the combined response endpoint. Existing response history is preserved; `MIGRATION.md` lists the contract changes. The generated Hephaestus web client is updated in the same release.
**Operators:** direct API callers must replace `/practice-areas` with `/practice-groups`, use the corresponding group schema and field names, replace `/practices/developer` with `/practices/reviewed`, and move from the older reaction endpoint to the combined response endpoint. Existing response history is preserved; `MIGRATION.md` lists the contract changes. The generated Hephaestus web client is updated in the same release.
Comment thread
FelixTJDietrich marked this conversation as resolved.
24 changes: 24 additions & 0 deletions .migration/practice-standing-feedback-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#### 🔴 The practice-area endpoints are replaced by practice-group endpoints

**Affected**: anything calling the application API directly — scripts, dashboards, or an integration
built against `/practice-areas` or the developer practice list. Deployments that only run the
bundled web client need no changes; it ships updated in this release.

**Before**: practice groupings were served under `/workspaces/{workspaceSlug}/practice-areas`, with
`PracticeArea` schemas and `areaSlug` parameters. A developer's practices came from
`/workspaces/{workspaceSlug}/practices/developer`, and a reaction to delivered feedback was recorded
through its own endpoint.

**After**: the same groupings are served under `/workspaces/{workspaceSlug}/practice-groups`, with
`PracticeGroup` schemas and `groupSlug` parameters — *practice area*, `PracticeArea`, `areaSlug` and
`/practice-areas` are retired names, not synonyms, and no alias remains. The developer practice list
is `/workspaces/{workspaceSlug}/practices/reviewed`. A developer's response to delivered feedback —
whether it was helpful, how it was handled, and an optional explanation — is written through one
combined response endpoint that replaces the earlier reaction endpoint. Every one of these answers
Comment thread
FelixTJDietrich marked this conversation as resolved.
only for the signed-in developer.

**Migration**: update each caller's paths, parameter names and response field names to the group
spelling, move any caller of `/practices/developer` to `/practices/reviewed`, and switch reaction
writes to the combined response endpoint. Response history recorded before the upgrade is preserved
and readable through the new endpoint, so nothing needs re-entering. Regenerate any client
built from `server/openapi.yaml`.
1 change: 1 addition & 0 deletions webapp/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const preview: Preview = {
"Instance admin",
"Practice trace",
"Workspace",
"Profile",
"Surveys",
"Common",
"Shared",
Expand Down
2 changes: 1 addition & 1 deletion webapp/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ page throws the reader back to the top.
|---|---|---|
| What an instance offers | **catalog** / instance catalog | library |
| Availability to workspaces | **include / exclude** | offer, retire |
| A grouping of practices | **group** (user-facing); `area` stays in code, types and the API | area, section |
| A grouping of practices | **group**, at every layer — copy, code, types and the API alike | area, section |
| A practice with no group | **Unassigned** | No area, Not in an area, Belong to no area |
| Inputs and criteria | **review rules** | review behavior, detection config |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const PEOPLE: ReviewPeople = {
isLoading: false,
isError: false,
};
const AREAS: FacetSource = {
const GROUPS: FacetSource = {
options: groupFacetOptions(practiceGroups),
isLoading: false,
isError: false,
Expand All @@ -48,7 +48,7 @@ const meta = {
search: { presence: undefined, assessment: undefined, severity: undefined },
onPatch: fn(),
onReset: fn(),
groups: AREAS,
groups: GROUPS,
practices: PRACTICES,
people: PEOPLE,
total: 12,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const PEOPLE: ReviewPeople = {
isLoading: false,
isError: false,
};
const AREAS: FacetSource = {
const GROUPS: FacetSource = {
options: groupFacetOptions(practiceGroups),
isLoading: false,
isError: false,
Expand Down Expand Up @@ -124,7 +124,7 @@ const meta = {
isLoading: false,
error: undefined,
onRetry: fn(),
groups: AREAS,
groups: GROUPS,
practices: PRACTICES,
// The facet needs a label per slug; the hover card on a row's practice name needs the record.
practiceRecords: workspacePractices,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { CircleCheckIcon, CircleSlashIcon, MessageCircleQuestionMarkIcon } from "lucide-react";

import type { PracticeGroupReviewObservation } from "@/api/types.gen";

import type { StatusDefs } from "./status-def";

export type FeedbackResolution = NonNullable<PracticeGroupReviewObservation["feedbackResolution"]>;

/**
* What the developer did about a piece of delivered feedback — the half of a response about the
* work, where `feedback-usefulness-defs` is about the review. The two are independent on the wire
* and stay independent here.
*
* `DISPUTED` is the only value the server demands an explanation for, so its description says so
* rather than leaving the reader to find out when the form refuses.
*/
export const FEEDBACK_RESOLUTION_DEFS: StatusDefs<FeedbackResolution> = {
ADDRESSED: {
label: "Addressed",
icon: CircleCheckIcon,
badgeVariant: "success",
description: "You changed the work in response to this.",
},
DISPUTED: {
label: "Disputed",
icon: MessageCircleQuestionMarkIcon,
badgeVariant: "warning",
description: "You disagree with this observation. Say why, so a human can weigh it.",
},
NOT_APPLICABLE: {
label: "Not applicable",
icon: CircleSlashIcon,
badgeVariant: "outline",
description: "The observation does not apply to this work.",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";

import type { PracticeGroupReviewObservation } from "@/api/types.gen";

import type { StatusDefs } from "./status-def";

export type FeedbackUsefulness = NonNullable<PracticeGroupReviewObservation["feedbackUsefulness"]>;

/**
* Whether delivered feedback was worth receiving — the half of a response about the review, where
* `feedback-resolution-defs` is about the work. Neither derives the other: feedback can be useful
* and still not apply.
*/
export const FEEDBACK_USEFULNESS_DEFS: StatusDefs<FeedbackUsefulness> = {
HELPFUL: {
label: "Helpful",
icon: ThumbsUpIcon,
badgeVariant: "success",
description: "This told you something you could act on.",
},
UNHELPFUL: {
label: "Not helpful",
icon: ThumbsDownIcon,
badgeVariant: "destructive",
description: "This was not worth the read.",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
CircleAlertIcon,
CircleCheckIcon,
CircleDashedIcon,
CircleMinusIcon,
CircleSlashIcon,
} from "lucide-react";

import type { PracticeGroupStanding } from "@/api/types.gen";

import type { StatusDef } from "./status-def";

export type PracticeGroupStandingValue = PracticeGroupStanding["standing"];

export interface PracticeGroupStandingDef extends StatusDef {
shortLabel: string;
}

/**
* Where a developer stands in a practice group, worst first. `DEVELOPING` means problems
* *predominate* — `MIXED` has them too — and `NO_OPPORTUNITY` covers evidence that settled nothing
* as well as work that offered no occasion.
*
* Both silences render outline, so the icon separates them: dashed for "nothing seen yet", a slash
* for "nothing to see". `shortLabel` is the ring legend's wording, where five entries share a row.
*/
export const PRACTICE_GROUP_STANDING_DEFS: Record<
PracticeGroupStandingValue,
PracticeGroupStandingDef
> = {
DEVELOPING: {
shortLabel: "Needs attention",
label: "Needs attention",
icon: CircleAlertIcon,
badgeVariant: "destructive",
description: "Recent reviews here were mostly problems.",
},
MIXED: {
shortLabel: "Mixed",
label: "Mixed feedback",
icon: CircleMinusIcon,
badgeVariant: "warning",
description: "Recent reviews found both strengths and problems here.",
},
STRENGTH: {
shortLabel: "Going well",
label: "Going well",
icon: CircleCheckIcon,
badgeVariant: "success",
description: "Recent reviews here were almost entirely positive.",
},
NO_OPPORTUNITY: {
shortLabel: "Nothing to report",
label: "Nothing to report yet",
icon: CircleSlashIcon,
badgeVariant: "outline",
description:
"Your work was reviewed, but nothing here could be judged — either these practices did not apply to it, or the evidence did not settle the question.",
},
NOT_OBSERVED: {
shortLabel: "Not observed",
label: "Not observed yet",
icon: CircleDashedIcon,
badgeVariant: "outline",
description: "No practice in this group has a current verdict for you.",
},
};
38 changes: 38 additions & 0 deletions webapp/src/components/practice-vocabulary/practice-trend-defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { CircleDashedIcon, CircleHelpIcon, TrendingDownIcon, TrendingUpIcon } from "lucide-react";

import type { PracticeTrend } from "@/api/types.gen";

import type { StatusDefs } from "./status-def";

export type TrendDirection = PracticeTrend["direction"];

/**
* Which way recent evidence points. `UNCERTAIN` means the comparison was made and did not separate;
* `INSUFFICIENT_EVIDENCE` that none was possible. Both render muted, so the icon separates them.
*/
export const PRACTICE_TREND_DEFS: StatusDefs<TrendDirection> = {
IMPROVING: {
label: "More positive recently",
icon: TrendingUpIcon,
badgeVariant: "success",
description: "Recent reviewed work carried more strengths than the stretch before it.",
},
DECLINING: {
label: "More difficulties recently",
icon: TrendingDownIcon,
badgeVariant: "destructive",
description: "Recent reviewed work carried more problems than the stretch before it.",
},
UNCERTAIN: {
label: "Direction unclear",
icon: CircleHelpIcon,
badgeVariant: "secondary",
description: "The two stretches were compared and did not separate far enough to call.",
},
INSUFFICIENT_EVIDENCE: {
label: "Not enough to compare yet",
icon: CircleDashedIcon,
badgeVariant: "outline",
description: "There is not yet enough reviewed work on both sides to compare.",
},
};
Loading
Loading