Sprint: Verbeteren debug mode (#179)
* feat(PBI-49): add debugProps helper + Vitest test
Adds lib/debug.ts with debugProps(id, component, file) that returns
data-debug-id and data-debug-label attrs in dev mode, empty object in
production. Adds __tests__/lib/debug.test.ts covering both modes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(PBI-49): add debug-id pattern doc + CLAUDE.md reference
Adds docs/patterns/debug-id.md documenting the named-component boundary
rule (6 punten), helper-voorbeeld, skip-criteria en motivatie voor
handmatige pad-argumenten. Voegt verwijzing toe aan CLAUDE.md
patterns-tabel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(PBI-49): migrate 17 shared/ components to debugProps helper
Replace hardcoded data-debug-id + data-debug-label attribute pairs with
{...debugProps(id, component, file)} spread in all 17 components/shared/
files. Existing debug-ids preserved unchanged.
* feat(PBI-49): add debugProps to backlog/, sprint/, solo/ components
* feat(PBI-49): add debugProps to jobs/ + ideas/ components
* feat(PBI-49): add debugProps to products/ + settings/ + notifications/ components
* feat(PBI-49): add debugProps to admin/ + dashboard/ + dialogs/ + mobile/ + split-pane/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(PBI-49): use attr(data-debug-id) for debug tooltip in globals.css
* refactor(PBI-49): remove data-debug-label from debugProps helper + test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(PBI-49): strip unused component/file args from debugProps in shared/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to StatusBar, NavBar, PanelNavBar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to components/sprint/*
- new-sprint-dialog: __submit on submit button
- sprint-backlog: __list on SprintBacklogLeft + SprintBacklogRight scroll areas
- sprint-board-client: root wrapper div (display:contents) + __drag-overlay
- sprint-header: __title on goal button, __dates on dates button, __actions on action cluster
- sprint-run-controls: root on controls div, __start/__cancel on action buttons; __blockers-dialog on dialog content
- start-sprint-button: root on trigger button, __dialog on dialog content, __submit on submit button
- sync-active-sprint-cookie: no debug-id (returns null, side-effect only), comment added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to components/backlog/*
* feat(PBI-49): add BEM sub-element data-debug-id to components/ideas/*
* feat(PBI-49): add BEM sub-element data-debug-id to components/dashboard/* + components/markdown.tsx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to new-product-button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to components/solo/*
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-elements to nav-status-indicators
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to components/jobs/*
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to components/products/*
* feat(PBI-49): add BEM sub-element data-debug-id to components/notifications/*
- answer-modal: __content (scroll area), __submit (footer)
- notifications-bridge: skip comment (bridge, non-rendering wrapper)
- notifications-realtime-mount: skip comment (returns null)
- notifications-sheet: __header, __items (questions list)
- push-toggle: __switch (button), __label (button text) on subscribed/unsubscribed states
* feat(PBI-49): add BEM sub-element data-debug-id to components/settings/*
- leave-product-button: root only (single-button component)
- min-quota-editor: __input (number input), __save (save button)
- profile-editor: __username (bio/short-description input), __save (submit)
- role-manager: __roles (checkbox list), __add (save button)
- token-manager: __tokens (active tokens list), __generate (create button)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(PBI-49): add BEM sub-element data-debug-id to admin, auth, dialogs, entity-dialog, mobile, split-pane
* docs(PBI-49): add debug-labels BEM pattern doc + CLAUDE.md entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ce43f7720a
commit
d292e445d9
93 changed files with 600 additions and 218 deletions
|
|
@ -8,6 +8,7 @@ import {
|
|||
entityDialogFooterClasses,
|
||||
entityDialogHeaderClasses,
|
||||
} from '@/components/shared/entity-dialog-layout'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface BatchEnqueueBlockerDialogProps {
|
||||
open: boolean
|
||||
|
|
@ -37,12 +38,12 @@ export function BatchEnqueueBlockerDialog({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent showCloseButton={false} className={entityDialogContentClasses}>
|
||||
<DialogContent showCloseButton={false} className={entityDialogContentClasses} {...debugProps('batch-enqueue-blocker-dialog', 'BatchEnqueueBlockerDialog', 'components/solo/batch-enqueue-blocker-dialog.tsx')}>
|
||||
<div className={entityDialogHeaderClasses}>
|
||||
<DialogTitle className="text-xl font-semibold">Blokkade gedetecteerd</DialogTitle>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-6 space-y-6 text-sm text-foreground">
|
||||
<div className="flex-1 overflow-y-auto px-6 py-6 space-y-6 text-sm text-foreground" data-debug-id="batch-enqueue-blocker-dialog__content">
|
||||
<p>
|
||||
{BLOCKER_REASON_LABELS[blockerReason]}:{' '}
|
||||
<span className="font-medium">{blockerLabel}</span>.
|
||||
|
|
@ -60,7 +61,7 @@ export function BatchEnqueueBlockerDialog({
|
|||
|
||||
<div className={entityDialogFooterClasses}>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="ghost" onClick={onCancel}>
|
||||
<Button variant="ghost" onClick={onCancel} data-debug-id="batch-enqueue-blocker-dialog__cancel">
|
||||
Annuleer
|
||||
</Button>
|
||||
<TooltipProvider>
|
||||
|
|
@ -71,6 +72,7 @@ export function BatchEnqueueBlockerDialog({
|
|||
<Button
|
||||
onClick={onConfirm}
|
||||
disabled={noTasksBeforeBlocker}
|
||||
data-debug-id="batch-enqueue-blocker-dialog__confirm"
|
||||
>
|
||||
{prefixCount === 1
|
||||
? `Stuur ${prefixCount} taak tot aan blokkade`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useSoloStore } from '@/stores/solo-store'
|
|||
import type { RealtimeStatus } from '@/stores/solo-store'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
function RealtimeIndicator({
|
||||
status,
|
||||
|
|
@ -63,12 +64,14 @@ export function SoloNavStatusIndicators({
|
|||
workerQuotaPct < minQuotaPct
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-2">
|
||||
<RealtimeIndicator
|
||||
status={realtimeStatus}
|
||||
showConnectingIndicator={showConnectingIndicator}
|
||||
/>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-3 px-2" {...debugProps('solo-nav-status-indicators', 'SoloNavStatusIndicators', 'components/solo/nav-status-indicators.tsx')}>
|
||||
<span data-debug-id="nav-status-indicators__queue">
|
||||
<RealtimeIndicator
|
||||
status={realtimeStatus}
|
||||
showConnectingIndicator={showConnectingIndicator}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground" data-debug-id="nav-status-indicators__running">
|
||||
<span className={cn(
|
||||
'size-2 rounded-full',
|
||||
isStandby
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Link from 'next/link'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface NoActiveSprintProps {
|
||||
productId: string
|
||||
|
|
@ -7,9 +8,9 @@ interface NoActiveSprintProps {
|
|||
|
||||
export function NoActiveSprint({ productId, productName }: NoActiveSprintProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4 text-center px-6">
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4 text-center px-6" {...debugProps('no-active-sprint', 'NoActiveSprint', 'components/solo/no-active-sprint.tsx')}>
|
||||
<div className="text-4xl text-muted-foreground">🏃</div>
|
||||
<h2 className="text-lg font-medium text-foreground">Geen actieve sprint</h2>
|
||||
<h2 className="text-lg font-medium text-foreground" data-debug-id="no-active-sprint__title">Geen actieve sprint</h2>
|
||||
<p className="text-sm text-muted-foreground max-w-sm">
|
||||
Er is nog geen actieve sprint voor <span className="font-medium text-foreground">{productName}</span>.
|
||||
Start een sprint in het Sprint Board om hier je taken te zien.
|
||||
|
|
@ -17,6 +18,7 @@ export function NoActiveSprint({ productId, productName }: NoActiveSprintProps)
|
|||
<Link
|
||||
href={`/products/${productId}/sprint`}
|
||||
className="text-sm text-primary hover:underline"
|
||||
data-debug-id="no-active-sprint__cta"
|
||||
>
|
||||
Naar Sprint Board →
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Link from 'next/link'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface Product {
|
||||
id: string
|
||||
|
|
@ -12,7 +13,7 @@ interface ProductPickerProps {
|
|||
|
||||
export function ProductPicker({ products }: ProductPickerProps) {
|
||||
return (
|
||||
<div className="p-6 max-w-2xl mx-auto w-full">
|
||||
<div className="p-6 max-w-2xl mx-auto w-full" {...debugProps('product-picker', 'ProductPicker', 'components/solo/product-picker.tsx')}>
|
||||
<h1 className="text-xl font-medium text-foreground mb-2">Solo bord</h1>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
Kies een product om je persoonlijke Kanban-bord te openen.
|
||||
|
|
@ -26,7 +27,7 @@ export function ProductPicker({ products }: ProductPickerProps) {
|
|||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-2" data-debug-id="product-picker__items">
|
||||
{products.map(product => (
|
||||
<Link
|
||||
key={product.id}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
import { useSoloRealtime } from '@/lib/realtime/use-solo-realtime'
|
||||
|
||||
// render-loos — geen JSX-root, data-debug-id niet van toepassing
|
||||
export function SoloRealtimeBridge({ productId }: { productId: string | null }) {
|
||||
useSoloRealtime(productId)
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { useSoloStore } from '@/stores/solo-store'
|
|||
import { taskStatusToApi } from '@/lib/task-status'
|
||||
import { previewEnqueueAllAction, enqueueClaudeJobsBatchAction } from '@/actions/claude-jobs'
|
||||
import { BatchEnqueueBlockerDialog } from './batch-enqueue-blocker-dialog'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
||||
import { SplitPane } from '@/components/split-pane/split-pane'
|
||||
|
|
@ -200,8 +201,8 @@ export function SoloBoard({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-4 gap-4 min-h-0">
|
||||
<div className="flex items-start justify-between gap-4 shrink-0">
|
||||
<div className="flex flex-col h-full p-4 gap-4 min-h-0" {...debugProps('solo-board', 'SoloBoard', 'components/solo/solo-board.tsx')}>
|
||||
<div className="flex items-start justify-between gap-4 shrink-0" data-debug-id="solo-board__header">
|
||||
<div className="min-w-0 flex items-center gap-3">
|
||||
<DemoTooltip show={isDemo}>
|
||||
<Button
|
||||
|
|
@ -231,7 +232,7 @@ export function SoloBoard({
|
|||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<div className="flex-1 min-h-0">
|
||||
<div className="flex-1 min-h-0" data-debug-id="solo-board__columns">
|
||||
<SplitPane
|
||||
cookieKey={`solo-${productId}`}
|
||||
defaultSplit={[33, 33, 34]}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { useDroppable } from '@dnd-kit/core'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SoloTaskCard } from './solo-task-card'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
import type { SoloTask } from './solo-board'
|
||||
|
||||
export const COLUMN_CONFIG = {
|
||||
|
|
@ -40,13 +41,14 @@ export function SoloColumn({ status, tasks, isDemo, onTaskClick }: SoloColumnPro
|
|||
'flex flex-col h-full rounded-lg border border-border overflow-hidden',
|
||||
isOver && 'ring-2 ring-primary ring-inset',
|
||||
)}
|
||||
{...debugProps('solo-column', 'SoloColumn', 'components/solo/solo-column.tsx')}
|
||||
>
|
||||
<div className={cn('flex items-center gap-2 px-3 py-2', config.headerClass)}>
|
||||
<div className={cn('flex items-center gap-2 px-3 py-2', config.headerClass)} data-debug-id="solo-column__header">
|
||||
<span className="text-sm font-medium">{config.label}</span>
|
||||
<span className="text-xs opacity-60 ml-auto">{tasks.length}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col gap-2 p-2 overflow-y-auto min-h-[140px]">
|
||||
<div className="flex-1 flex flex-col gap-2 p-2 overflow-y-auto min-h-[140px]" data-debug-id="solo-column__tasks">
|
||||
{tasks.map(task => (
|
||||
<SoloTaskCard
|
||||
key={task.id}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { JOB_STATUS_LABELS, JOB_STATUS_COLORS, JOB_STATUS_ACTIVE } from '@/compo
|
|||
import { useSoloStore } from '@/stores/solo-store'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
|
||||
import type { SoloTask } from './solo-board'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
const PRIORITY_BORDER: Record<number, string> = {
|
||||
1: 'border-l-4 border-l-priority-critical',
|
||||
|
|
@ -47,9 +48,10 @@ export function SoloTaskCard({ task, isDemo, onClick }: SoloTaskCardProps) {
|
|||
isDemo ? 'cursor-pointer' : 'cursor-grab active:cursor-grabbing',
|
||||
)}
|
||||
{...(!isDemo ? { ...attributes, ...listeners } : {})}
|
||||
{...debugProps('solo-task-card', 'SoloTaskCard', 'components/solo/solo-task-card.tsx')}
|
||||
>
|
||||
{/* Regel 1: taaknaam + task_code */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex items-start justify-between gap-2" data-debug-id="solo-task-card__title">
|
||||
<p className="text-sm text-foreground leading-snug flex-1">{task.title}</p>
|
||||
{task.task_code && (
|
||||
<TooltipProvider>
|
||||
|
|
@ -106,7 +108,7 @@ export function SoloTaskCard({ task, isDemo, onClick }: SoloTaskCardProps) {
|
|||
</div>
|
||||
|
||||
{/* Regel 4: story-info + job-badge */}
|
||||
<div className="flex items-center justify-between gap-2 mt-0.5">
|
||||
<div className="flex items-center justify-between gap-2 mt-0.5" data-debug-id="solo-task-card__status">
|
||||
<p className="text-xs text-muted-foreground truncate flex-1">
|
||||
{task.story_code && <span className="font-mono mr-1">{task.story_code}</span>}
|
||||
{task.story_title}
|
||||
|
|
@ -145,6 +147,7 @@ export function SoloTaskCardOverlay({ task }: { task: SoloTask }) {
|
|||
'bg-surface-container rounded border border-primary px-3 py-2 shadow-xl opacity-90',
|
||||
PRIORITY_BORDER[task.priority],
|
||||
)}
|
||||
{...debugProps('solo-task-card-overlay', 'SoloTaskCardOverlay', 'components/solo/solo-task-card.tsx')}
|
||||
>
|
||||
{/* Regel 1 */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { useSoloStore } from '@/stores/solo-store'
|
|||
import { enqueueClaudeJobAction, cancelClaudeJobAction } from '@/actions/claude-jobs'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getBranchUrl } from '@/lib/job-status-url'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
import type { SoloTask } from './solo-board'
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
|
|
@ -206,7 +207,7 @@ function TaskDetailContent({ task, productId, isDemo, repoUrl, onClose }: TaskDe
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div className={entityDialogBodyClasses}>
|
||||
<div className={entityDialogBodyClasses} data-debug-id="task-detail-dialog__content">
|
||||
{task.description && (
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5">Beschrijving</p>
|
||||
|
|
@ -380,7 +381,7 @@ function TaskDetailContent({ task, productId, isDemo, repoUrl, onClose }: TaskDe
|
|||
export function TaskDetailDialog({ task, productId, isDemo, repoUrl, onClose }: TaskDetailDialogProps) {
|
||||
return (
|
||||
<Dialog open={!!task} onOpenChange={(open) => { if (!open) onClose() }}>
|
||||
<DialogContent showCloseButton={false} className={entityDialogContentClasses}>
|
||||
<DialogContent showCloseButton={false} className={entityDialogContentClasses} {...debugProps('task-detail-dialog', 'TaskDetailDialog', 'components/solo/task-detail-dialog.tsx')}>
|
||||
{task && (
|
||||
<TaskDetailContent
|
||||
key={task.id}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
|||
import { CodeBadge } from '@/components/shared/code-badge'
|
||||
import { claimStoryAction } from '@/actions/stories'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
export interface UnassignedStoryTask {
|
||||
id: string
|
||||
|
|
@ -156,12 +157,12 @@ export function UnassignedStoriesSheet({
|
|||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="right">
|
||||
<SheetContent side="right" {...debugProps('unassigned-stories-sheet', 'UnassignedStoriesSheet', 'components/solo/unassigned-stories-sheet.tsx')}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Openstaande stories</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex-1 overflow-y-auto" data-debug-id="unassigned-stories-sheet__content">
|
||||
{stories.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
|
|
@ -169,7 +170,7 @@ export function UnassignedStoriesSheet({
|
|||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2" data-debug-id="unassigned-stories-sheet__items">
|
||||
{stories.map(story => (
|
||||
<ClaimStoryRow
|
||||
key={story.id}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue