Skip to content

Commit dd04e6b

Browse files
authored
feat: add Conventional Comments support to code review (#501)
* feat: add Conventional Comments support to code review Add structured label system based on the Conventional Comments spec (conventionalcomments.org) to the code review annotation toolbar. - Label picker with 9 default labels (suggestion, nit, question, issue, praise, thought, note, todo, chore) shown above the comment textarea - Per-label blocking/non-blocking toggle for labels where severity is ambiguous (issue, suggestion, todo, chore) - Labels exported as `**label** (decoration): text` in markdown feedback, naturally readable on GitHub and machine-parseable - Label badges shown on inline annotations and sidebar cards - Settings tab (Comments) with: master on/off toggle, editable label list with add/remove/rename, per-label blocking decorator toggle, educational intro with spec link and example output - Feature is off by default, persisted via config store - Custom labels supported via settings (up to 12) For provenance purposes, this commit was AI assisted. * fix(review): address conventional comments review feedback Fix label clearing bug where editing an annotation to remove its conventional label silently preserved the old value. Fix parseCCLabels accepting 'non-blocking' as a truthy blocking value. Fix duplicate custom label identities when adding multiple labels. Prepend conventional prefix to platform PR/MR inline comments. For provenance purposes, this commit was AI assisted. * fix(server): persist conventionalComments setting to config.json All six server config handlers (bun plan/review/annotate + pi plan/review/annotate) now accept and save the conventionalComments boolean, so the setting survives server restarts. For provenance purposes, this commit was AI assisted. * fix: add conventionalComments to server config round-trip getServerConfig() was not returning the conventionalComments value from config.json, so the setting silently reset to false on cookie clear or new browser sessions. Also adds the field to the PlannotatorConfig interface so saveConfig() is properly typed. For provenance purposes, this commit was AI assisted. * fix: label display, export, and draft persistence for conventional comments - Remove slug derivation from label editing — store display text verbatim so badges and exports show what the user typed - Emit conventional prefix in export even when annotation has no prose text (suggestion-only annotations were losing their label) - Include conventionalLabel in draft save condition so label selection persists across file switches For provenance purposes, this commit was AI assisted. * fix: spec-compliant export format and empty label support - Wrap label + decorations + colon in markdown bold per the Conventional Comments spec examples (was bolding only the label) - Allow users to clear all labels — empty array no longer falls back to defaults in either the picker or the settings parser - Enable blocking decorator on the question label by default; the spec permits blocking questions and they sometimes are critical For provenance purposes, this commit was AI assisted. * fix: server-sync conventionalLabels and small cleanups - Sync conventionalLabels to ~/.plannotator/config.json so custom label configurations survive cookie clears and remote/SSH sessions, matching the persistence story for conventionalComments. Stored as a parsed array on disk; serialized as JSON string in the local config store to match the cookie format. - Remove no-op intermediate variable in handleSubmitAnnotation; pass conventionalLabel/decorations directly with proper coalescing per call path (edit preserves null, add coalesces to undefined). - Use index-prefixed key in the picker label list to avoid React warnings if duplicate labels ever exist. For provenance purposes, this commit was AI assisted.
1 parent 719fd96 commit dd04e6b

File tree

21 files changed

+816
-32
lines changed

21 files changed

+816
-32
lines changed

apps/pi-extension/server/serverAnnotate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ export async function startAnnotateServer(options: {
8484
});
8585
} else if (url.pathname === "/api/config" && req.method === "POST") {
8686
try {
87-
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
87+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown>; conventionalComments?: boolean };
8888
const toSave: Record<string, unknown> = {};
8989
if (body.displayName !== undefined) toSave.displayName = body.displayName;
9090
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
91+
if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments;
9192
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
9293
json(res, { ok: true });
9394
} catch {

apps/pi-extension/server/serverPlan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ export async function startPlanReviewServer(options: {
225225
}
226226
} else if (url.pathname === "/api/config" && req.method === "POST") {
227227
try {
228-
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
228+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown>; conventionalComments?: boolean };
229229
const toSave: Record<string, unknown> = {};
230230
if (body.displayName !== undefined) toSave.displayName = body.displayName;
231231
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
232+
if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments;
232233
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
233234
json(res, { ok: true });
234235
} catch {

apps/pi-extension/server/serverReview.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,10 +589,11 @@ export async function startReviewServer(options: {
589589
json(res, { error: "No file access available" }, 400);
590590
} else if (url.pathname === "/api/config" && req.method === "POST") {
591591
try {
592-
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
592+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown>; conventionalComments?: boolean };
593593
const toSave: Record<string, unknown> = {};
594594
if (body.displayName !== undefined) toSave.displayName = body.displayName;
595595
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
596+
if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments;
596597
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
597598
json(res, { ok: true });
598599
} catch {

packages/review-editor/App.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { getAgentSwitchSettings, getEffectiveAgentName } from '@plannotator/ui/u
1818
import { getAIProviderSettings, saveAIProviderSettings, getPreferredModel } from '@plannotator/ui/utils/aiProvider';
1919
import { AISetupDialog } from '@plannotator/ui/components/AISetupDialog';
2020
import { needsAISetup } from '@plannotator/ui/utils/aiSetup';
21-
import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, TokenAnnotationMeta } from '@plannotator/ui/types';
21+
import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, TokenAnnotationMeta, ConventionalLabel, ConventionalDecoration } from '@plannotator/ui/types';
2222
import { useResizablePanel } from '@plannotator/ui/hooks/useResizablePanel';
2323
import { useCodeAnnotationDraft } from '@plannotator/ui/hooks/useCodeAnnotationDraft';
2424
import { useGitAdd } from './hooks/useGitAdd';
@@ -36,7 +36,7 @@ import { ReviewHeaderMenu } from './components/ReviewHeaderMenu';
3636
import { ReviewSidebar } from './components/ReviewSidebar';
3737
import { FileTree } from './components/FileTree';
3838
import { DEMO_DIFF } from './demoData';
39-
import { exportReviewFeedback } from './utils/exportFeedback';
39+
import { exportReviewFeedback, formatConventionalPrefix } from './utils/exportFeedback';
4040
import { ReviewStateProvider, type ReviewState } from './dock/ReviewStateContext';
4141
import { JobLogsProvider } from './dock/JobLogsContext';
4242
import { reviewPanelComponents } from './dock/reviewPanelComponents';
@@ -688,6 +688,8 @@ const ReviewApp: React.FC = () => {
688688
text?: string,
689689
suggestedCode?: string,
690690
originalCode?: string,
691+
conventionalLabel?: ConventionalLabel,
692+
decorations?: ConventionalDecoration[],
691693
tokenMeta?: TokenAnnotationMeta
692694
) => {
693695
if (!pendingSelection || !files[activeFileIndex]) return;
@@ -714,6 +716,8 @@ const ReviewApp: React.FC = () => {
714716
}),
715717
createdAt: Date.now(),
716718
author: identity,
719+
conventionalLabel,
720+
decorations,
717721
};
718722

719723
setAnnotations(prev => [...prev, newAnnotation]);
@@ -746,13 +750,18 @@ const ReviewApp: React.FC = () => {
746750
id: string,
747751
text?: string,
748752
suggestedCode?: string,
749-
originalCode?: string
753+
originalCode?: string,
754+
conventionalLabel?: ConventionalLabel | null,
755+
decorations?: ConventionalDecoration[],
750756
) => {
751757
const ann = allAnnotationsRef.current.find(a => a.id === id);
752-
const updates = {
758+
const updates: Partial<CodeAnnotation> = {
753759
...(text !== undefined && { text }),
754760
...(suggestedCode !== undefined && { suggestedCode }),
755761
...(originalCode !== undefined && { originalCode }),
762+
// null clears the label; undefined means "not provided, keep existing"
763+
...(conventionalLabel !== undefined && { conventionalLabel: conventionalLabel ?? undefined }),
764+
...(decorations !== undefined && { decorations }),
756765
};
757766
if (ann?.source && externalAnnotations.some(e => e.id === id)) {
758767
updateExternalAnnotation(id, updates);
@@ -1124,7 +1133,8 @@ const ReviewApp: React.FC = () => {
11241133

11251134
// Inline file comments
11261135
const fileComments = fileAnnotations.map(ann => {
1127-
let commentBody = ann.text ?? '';
1136+
const ccPrefix = formatConventionalPrefix(ann.conventionalLabel, ann.decorations);
1137+
let commentBody = ccPrefix + (ann.text ?? '');
11281138
if (ann.suggestedCode) {
11291139
commentBody += `\n\n\`\`\`suggestion\n${ann.suggestedCode}\n\`\`\``;
11301140
}

packages/review-editor/components/AnnotationToolbar.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { useTabIndent } from '../hooks/useTabIndent';
55
import { formatLineRange, formatTokenContext } from '../utils/formatLineRange';
66
import { AskAIInput } from './AskAIInput';
77
import { SparklesIcon } from './SparklesIcon';
8+
import { ConventionalLabelPicker, type LabelDef } from './ConventionalLabelPicker';
9+
import type { ConventionalLabel, ConventionalDecoration } from '@plannotator/ui/types';
810
import type { AIChatEntry } from '../hooks/useAIChat';
911
import { useDraggable } from '@plannotator/ui/hooks/useDraggable';
1012

@@ -23,6 +25,13 @@ interface AnnotationToolbarProps {
2325
onSubmit: () => void;
2426
onDismiss: () => void;
2527
onCancel: () => void;
28+
// Conventional Comments
29+
conventionalCommentsEnabled: boolean;
30+
conventionalLabel: ConventionalLabel | null;
31+
onConventionalLabelChange: (label: ConventionalLabel | null) => void;
32+
decorations: ConventionalDecoration[];
33+
onDecorationsChange: (decorations: ConventionalDecoration[]) => void;
34+
enabledLabels?: LabelDef[];
2635
// AI props
2736
aiAvailable?: boolean;
2837
onAskAI?: (question: string) => void;
@@ -48,6 +57,12 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
4857
onSubmit,
4958
onDismiss,
5059
onCancel,
60+
conventionalCommentsEnabled,
61+
conventionalLabel,
62+
onConventionalLabelChange,
63+
decorations,
64+
onDecorationsChange,
65+
enabledLabels,
5166
aiAvailable = false,
5267
onAskAI,
5368
isAILoading = false,
@@ -133,6 +148,16 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
133148
</button>
134149
</div>
135150

151+
{conventionalCommentsEnabled && (
152+
<ConventionalLabelPicker
153+
selected={conventionalLabel}
154+
decorations={decorations}
155+
onSelect={onConventionalLabelChange}
156+
onDecorationsChange={onDecorationsChange}
157+
enabledLabels={enabledLabels}
158+
/>
159+
)}
160+
136161
<textarea
137162
value={commentText}
138163
onChange={(e) => setCommentText(e.target.value)}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React, { useCallback } from 'react';
2+
import type { ConventionalLabel, ConventionalDecoration } from '@plannotator/ui/types';
3+
4+
/** Semantic tone — maps to theme CSS variables, not arbitrary hex */
5+
type SemanticTone = 'danger' | 'warn' | 'success' | 'info' | 'neutral';
6+
7+
export interface LabelDef {
8+
label: ConventionalLabel;
9+
display: string;
10+
tone: SemanticTone;
11+
/** Whether the blocking/non-blocking toggle shows in the picker for this label */
12+
showBlockingToggle: boolean;
13+
hint: string;
14+
}
15+
16+
export const CONVENTIONAL_LABELS: LabelDef[] = [
17+
// Everyday — the three you reach for on 90%+ of comments
18+
{ label: 'suggestion', display: 'suggestion', tone: 'info', showBlockingToggle: true, hint: 'Proposes an improvement' },
19+
{ label: 'nitpick', display: 'nit', tone: 'neutral', showBlockingToggle: false, hint: 'Trivial, preference-based' },
20+
{ label: 'question', display: 'question', tone: 'info', showBlockingToggle: true, hint: 'Seeking clarification' },
21+
// High-signal
22+
{ label: 'issue', display: 'issue', tone: 'danger', showBlockingToggle: true, hint: 'A problem that needs addressing' },
23+
{ label: 'praise', display: 'praise', tone: 'success', showBlockingToggle: false, hint: 'Highlight something positive' },
24+
// Soft / meta
25+
{ label: 'thought', display: 'thought', tone: 'neutral', showBlockingToggle: false, hint: 'An idea, not a request' },
26+
{ label: 'note', display: 'note', tone: 'neutral', showBlockingToggle: false, hint: 'Informational, no action needed' },
27+
// Process
28+
{ label: 'todo', display: 'todo', tone: 'warn', showBlockingToggle: true, hint: 'Small, necessary change' },
29+
{ label: 'chore', display: 'chore', tone: 'warn', showBlockingToggle: true, hint: 'Process task (CI, changelog, etc.)' },
30+
];
31+
32+
// ---------------------------------------------------------------------------
33+
// Picker
34+
// ---------------------------------------------------------------------------
35+
36+
/** Resolve which labels to show based on user config (null = all defaults; empty array = user cleared all) */
37+
export function getEnabledLabels(configJson: string | null): LabelDef[] {
38+
if (!configJson) return CONVENTIONAL_LABELS;
39+
try {
40+
const parsed = JSON.parse(configJson) as Array<Record<string, unknown>>;
41+
if (!Array.isArray(parsed)) return CONVENTIONAL_LABELS;
42+
return parsed.map(cfg => {
43+
const builtIn = CONVENTIONAL_LABELS.find(l => l.label === cfg.label);
44+
return {
45+
label: cfg.label as ConventionalLabel,
46+
display: cfg.display as string,
47+
tone: builtIn?.tone || 'neutral',
48+
showBlockingToggle: cfg.blocking === true || cfg.blocking === 'true',
49+
hint: builtIn?.hint || (cfg.display as string),
50+
};
51+
});
52+
} catch {
53+
return CONVENTIONAL_LABELS;
54+
}
55+
}
56+
57+
interface ConventionalLabelPickerProps {
58+
selected: ConventionalLabel | null;
59+
decorations: ConventionalDecoration[];
60+
onSelect: (label: ConventionalLabel | null) => void;
61+
onDecorationsChange: (decorations: ConventionalDecoration[]) => void;
62+
/** Filtered label list from config (defaults to all) */
63+
enabledLabels?: LabelDef[];
64+
}
65+
66+
export const ConventionalLabelPicker: React.FC<ConventionalLabelPickerProps> = ({
67+
selected,
68+
decorations,
69+
onSelect,
70+
onDecorationsChange,
71+
enabledLabels = CONVENTIONAL_LABELS,
72+
}) => {
73+
const handleLabelClick = useCallback((label: ConventionalLabel) => {
74+
if (selected === label) {
75+
onSelect(null);
76+
onDecorationsChange([]);
77+
} else {
78+
onSelect(label);
79+
const def = enabledLabels.find(l => l.label === label);
80+
// If blocking toggle is enabled for this label, default to non-blocking
81+
if (def?.showBlockingToggle) {
82+
onDecorationsChange(['non-blocking']);
83+
} else {
84+
onDecorationsChange([]);
85+
}
86+
}
87+
}, [selected, enabledLabels, onSelect, onDecorationsChange]);
88+
89+
const activeDef = selected ? enabledLabels.find(l => l.label === selected) : undefined;
90+
const isBlocking = decorations.includes('blocking');
91+
const showToggle = activeDef?.showBlockingToggle ?? false;
92+
93+
const toggleBlocking = useCallback(() => {
94+
const cleaned = decorations.filter(d => d !== 'blocking' && d !== 'non-blocking');
95+
onDecorationsChange([...cleaned, isBlocking ? 'non-blocking' : 'blocking']);
96+
}, [decorations, isBlocking, onDecorationsChange]);
97+
98+
return (
99+
<div className="cc-picker">
100+
<div className="cc-row">
101+
{enabledLabels.map((def, idx) => {
102+
const isActive = selected === def.label;
103+
return (
104+
<button
105+
key={`${idx}-${def.label}`}
106+
type="button"
107+
className={`cc-tag cc-tone-${def.tone}${isActive ? ' active' : ''}`}
108+
onClick={() => handleLabelClick(def.label)}
109+
title={def.hint}
110+
>
111+
{def.display}
112+
</button>
113+
);
114+
})}
115+
116+
{/* Blocking toggle — shown when enabled for this label */}
117+
{showToggle && (
118+
<button
119+
type="button"
120+
className={`cc-blocking-toggle ${isBlocking ? 'is-blocking' : ''}`}
121+
onClick={toggleBlocking}
122+
title={isBlocking ? 'Must resolve before merge' : 'Optional, not blocking'}
123+
>
124+
<span className="cc-toggle-track">
125+
<span className="cc-toggle-thumb" />
126+
</span>
127+
{isBlocking ? 'blocking' : 'non-blocking'}
128+
</button>
129+
)}
130+
</div>
131+
</div>
132+
);
133+
};
134+
135+
// ---------------------------------------------------------------------------
136+
// Badge (inline annotation header)
137+
// ---------------------------------------------------------------------------
138+
139+
export const ConventionalLabelBadge: React.FC<{
140+
label: ConventionalLabel;
141+
decorations?: ConventionalDecoration[];
142+
}> = ({ label, decorations }) => {
143+
const def = CONVENTIONAL_LABELS.find(l => l.label === label);
144+
// Fall back gracefully for custom labels not in the built-in list
145+
const tone = def?.tone || 'neutral';
146+
const display = def?.display || label;
147+
148+
const isBlocking = decorations?.includes('blocking');
149+
const hasDecoration = isBlocking || decorations?.includes('non-blocking');
150+
151+
return (
152+
<span className={`cc-inline-badge cc-tone-${tone}`}>
153+
{display}
154+
{hasDecoration && (
155+
<span className={`cc-inline-dec${isBlocking ? ' cc-inline-dec-blocking' : ''}`}>
156+
{isBlocking ? 'blocking' : 'non-blocking'}
157+
</span>
158+
)}
159+
</span>
160+
);
161+
};

packages/review-editor/components/DiffViewer.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import React, { useMemo, useRef, useEffect, useLayoutEffect, useCallback, useState } from 'react';
22
import { FileDiff, type DiffLineAnnotation } from '@pierre/diffs/react';
33
import { getSingularPatch, processFile } from '@pierre/diffs';
4-
import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, DiffAnnotationMetadata, TokenAnnotationMeta } from '@plannotator/ui/types';
4+
import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, DiffAnnotationMetadata, TokenAnnotationMeta, ConventionalLabel, ConventionalDecoration } from '@plannotator/ui/types';
55
import type { DiffTokenEventBaseProps } from '@pierre/diffs';
66
import { useTheme } from '@plannotator/ui/components/ThemeProvider';
77
import { CommentPopover } from '@plannotator/ui/components/CommentPopover';
88
import { storage } from '@plannotator/ui/utils/storage';
99
import { detectLanguage } from '../utils/detectLanguage';
1010
import { useAnnotationToolbar } from '../hooks/useAnnotationToolbar';
11+
import { useConfigValue } from '@plannotator/ui/config';
12+
import { getEnabledLabels } from './ConventionalLabelPicker';
1113
import { FileHeader } from './FileHeader';
1214
import { InlineAnnotation } from './InlineAnnotation';
1315
import { InlineAIMarker } from './InlineAIMarker';
@@ -127,9 +129,9 @@ interface DiffViewerProps {
127129
selectedAnnotationId: string | null;
128130
pendingSelection: SelectedLineRange | null;
129131
onLineSelection: (range: SelectedLineRange | null) => void;
130-
onAddAnnotation: (type: CodeAnnotationType, text?: string, suggestedCode?: string, originalCode?: string, tokenMeta?: TokenAnnotationMeta) => void;
132+
onAddAnnotation: (type: CodeAnnotationType, text?: string, suggestedCode?: string, originalCode?: string, conventionalLabel?: ConventionalLabel, decorations?: ConventionalDecoration[], tokenMeta?: TokenAnnotationMeta) => void;
131133
onAddFileComment: (text: string) => void;
132-
onEditAnnotation: (id: string, text?: string, suggestedCode?: string, originalCode?: string) => void;
134+
onEditAnnotation: (id: string, text?: string, suggestedCode?: string, originalCode?: string, conventionalLabel?: ConventionalLabel | null, decorations?: ConventionalDecoration[]) => void;
133135
onSelectAnnotation: (id: string | null) => void;
134136
onDeleteAnnotation: (id: string) => void;
135137
isViewed?: boolean;
@@ -252,6 +254,9 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
252254
}, []);
253255

254256
const toolbar = useAnnotationToolbar({ patch, filePath, isFocused, onLineSelection, onAddAnnotation, onEditAnnotation });
257+
const conventionalCommentsEnabled = useConfigValue('conventionalComments');
258+
const conventionalLabelsJson = useConfigValue('conventionalLabels');
259+
const enabledLabels = useMemo(() => getEnabledLabels(conventionalLabelsJson), [conventionalLabelsJson]);
255260

256261
// Parse patch into FileDiffMetadata for @pierre/diffs FileDiff component
257262
const fileDiff = useMemo(() => getSingularPatch(patch), [patch]);
@@ -375,6 +380,8 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
375380
author: ann.author,
376381
severity: ann.severity,
377382
reasoning: ann.reasoning,
383+
conventionalLabel: ann.conventionalLabel,
384+
decorations: ann.decorations,
378385
} as DiffAnnotationMetadata,
379386
}));
380387
}, [annotations]);
@@ -618,6 +625,12 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
618625
onSubmit={toolbar.handleSubmitAnnotation}
619626
onDismiss={toolbar.handleDismiss}
620627
onCancel={toolbar.handleCancel}
628+
conventionalCommentsEnabled={conventionalCommentsEnabled}
629+
conventionalLabel={toolbar.conventionalLabel}
630+
onConventionalLabelChange={toolbar.setConventionalLabel}
631+
decorations={toolbar.decorations}
632+
onDecorationsChange={toolbar.setDecorations}
633+
enabledLabels={enabledLabels}
621634
aiAvailable={aiAvailable}
622635
onAskAI={onAskAI}
623636
isAILoading={isAILoading}

packages/review-editor/components/InlineAnnotation.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { SEVERITY_STYLES, DiffAnnotationMetadata } from '@plannotator/ui/types';
33
import { SuggestionBlock } from './SuggestionBlock';
4+
import { ConventionalLabelBadge } from './ConventionalLabelPicker';
45
import { renderInlineMarkdown } from '../utils/renderInlineMarkdown';
56

67
interface InlineAnnotationProps {
@@ -32,6 +33,9 @@ export const InlineAnnotation: React.FC<InlineAnnotationProps> = ({
3233
{severity && (
3334
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${severity.dot}`} title={severity.label} />
3435
)}
36+
{metadata.conventionalLabel && (
37+
<ConventionalLabelBadge label={metadata.conventionalLabel} decorations={metadata.decorations} />
38+
)}
3539
{metadata.author && <span className="text-xs text-muted-foreground">{metadata.author}</span>}
3640
</div>
3741
<div className="review-comment-actions">

0 commit comments

Comments
 (0)