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
|
|
@ -5,6 +5,7 @@ import { useTransition } from 'react'
|
|||
import { toast } from 'sonner'
|
||||
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
||||
import { setActiveProductAction } from '@/actions/active-product'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface Props {
|
||||
productId: string
|
||||
|
|
@ -28,7 +29,7 @@ export function ActivateProductButton({ productId, isDemo, redirectTo, label = '
|
|||
}
|
||||
|
||||
return (
|
||||
<span data-debug-id="activate-product-button" data-debug-label="ActivateProductButton — shared/activate-product-button.tsx">
|
||||
<span {...debugProps('activate-product-button')}>
|
||||
<DemoTooltip show={isDemo}>
|
||||
<button
|
||||
onClick={() => !isDemo && handleActivate()}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
|
||||
import { toast } from 'sonner'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
const ALERT_MESSAGES: Record<string, string> = {
|
||||
product_unavailable: 'Je actieve product is niet meer beschikbaar',
|
||||
|
|
@ -24,5 +25,5 @@ export function AlertToast() {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [alert])
|
||||
|
||||
return <span data-debug-id="alert-toast" data-debug-label="AlertToast — shared/alert-toast.tsx" hidden />
|
||||
return <span {...debugProps('alert-toast')} hidden />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface AppIconProps {
|
||||
size?: number
|
||||
className?: string
|
||||
|
|
@ -13,8 +15,7 @@ export function AppIcon({ size = 32, className }: AppIconProps) {
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
aria-label="Scrum4Me"
|
||||
data-debug-id="app-icon"
|
||||
data-debug-label="AppIcon — shared/app-icon.tsx"
|
||||
{...debugProps('app-icon')}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="s4m-bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface CodeBadgeProps {
|
||||
code: string | null | undefined
|
||||
|
|
@ -9,8 +10,7 @@ export function CodeBadge({ code, className }: CodeBadgeProps) {
|
|||
if (!code) return null
|
||||
return (
|
||||
<span
|
||||
data-debug-id="code-badge"
|
||||
data-debug-label="CodeBadge — shared/code-badge.tsx"
|
||||
{...debugProps('code-badge')}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-md border border-border bg-surface-container px-1.5 py-0.5 font-mono text-[11px] leading-none text-muted-foreground',
|
||||
className,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface DemoTooltipProps {
|
||||
show: boolean
|
||||
|
|
@ -10,10 +11,10 @@ interface DemoTooltipProps {
|
|||
// Wraps children with a "Niet beschikbaar in demo-modus" tooltip when show=true.
|
||||
// Uses a span trigger so tooltip works on disabled elements.
|
||||
export function DemoTooltip({ show, children }: DemoTooltipProps) {
|
||||
if (!show) return <span data-debug-id="demo-tooltip" data-debug-label="DemoTooltip — shared/demo-tooltip.tsx">{children}</span>
|
||||
if (!show) return <span {...debugProps('demo-tooltip')}>{children}</span>
|
||||
|
||||
return (
|
||||
<span data-debug-id="demo-tooltip" data-debug-label="DemoTooltip — shared/demo-tooltip.tsx">
|
||||
<span {...debugProps('demo-tooltip')}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={<span className="inline-flex" />}>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
'use client'
|
||||
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
// Shows a warning banner on screens narrower than 1024px.
|
||||
export function MinWidthBanner() {
|
||||
return (
|
||||
<div
|
||||
data-debug-id="min-width-banner"
|
||||
data-debug-label="MinWidthBanner — shared/min-width-banner.tsx"
|
||||
{...debugProps('min-width-banner')}
|
||||
className="lg:hidden bg-warning/10 border-b border-warning/30 px-4 py-2 text-center text-xs text-warning"
|
||||
>
|
||||
Scrum4Me is ontworpen voor schermen van minimaal 1024px breed. Sommige functies zijn mogelijk niet goed bruikbaar op dit scherm.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { NotificationsBell } from '@/components/shared/notifications-bell'
|
|||
import { SoloNavStatusIndicators } from '@/components/solo/nav-status-indicators'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { setActiveProductAction } from '@/actions/active-product'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface NavBarProps {
|
||||
isDemo: boolean
|
||||
|
|
@ -112,13 +113,12 @@ export function NavBar({
|
|||
|
||||
return (
|
||||
<header
|
||||
data-debug-id="nav-bar"
|
||||
data-debug-label="NavBar — shared/nav-bar.tsx"
|
||||
{...debugProps('nav-bar')}
|
||||
className="bg-surface-container-low border-b border-border h-14 flex items-center px-4 shrink-0"
|
||||
>
|
||||
{/* Links: logo + nav */}
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<Link href="/" className="flex items-center gap-2 font-medium text-foreground">
|
||||
<Link href="/" data-debug-id="nav-bar__app-icon" className="flex items-center gap-2 font-medium text-foreground">
|
||||
<AppIcon size={24} />
|
||||
<span className="text-primary font-semibold">Scrum4Me</span>
|
||||
{isDemo && (
|
||||
|
|
@ -157,6 +157,7 @@ export function NavBar({
|
|||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
disabled={isPending}
|
||||
data-debug-id="nav-bar__product-switcher"
|
||||
className="flex items-center gap-1 text-sm font-medium text-foreground hover:text-primary transition-colors px-2 rounded-md hover:bg-surface-container focus:outline-none"
|
||||
>
|
||||
<span className="truncate max-w-[180px]">
|
||||
|
|
@ -195,7 +196,9 @@ export function NavBar({
|
|||
<div className="flex items-center gap-2 flex-1 justify-end">
|
||||
<SoloNavStatusIndicators hasActiveProduct={!!activeProduct} minQuotaPct={minQuotaPct} />
|
||||
<NotificationsBell currentUserId={userId} isDemo={isDemo} />
|
||||
<UserMenu userId={userId} username={username} email={email} roles={roles} />
|
||||
<span data-debug-id="nav-bar__user-menu">
|
||||
<UserMenu userId={userId} username={username} email={email} roles={roles} />
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Bell } from 'lucide-react'
|
|||
import { useNotificationsStore } from '@/stores/notifications-store'
|
||||
import { NotificationsSheet } from '@/components/notifications/notifications-sheet'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface NotificationsBellProps {
|
||||
currentUserId: string
|
||||
|
|
@ -27,7 +28,7 @@ export function NotificationsBell({ currentUserId, isDemo }: NotificationsBellPr
|
|||
)
|
||||
|
||||
return (
|
||||
<span data-debug-id="notifications-bell" data-debug-label="NotificationsBell — shared/notifications-bell.tsx">
|
||||
<span {...debugProps('notifications-bell')}>
|
||||
<NotificationsSheet
|
||||
currentUserId={currentUserId}
|
||||
isDemo={isDemo}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface PanelNavBarProps {
|
||||
title: string
|
||||
|
|
@ -9,12 +10,11 @@ interface PanelNavBarProps {
|
|||
export function PanelNavBar({ title, actions, className }: PanelNavBarProps) {
|
||||
return (
|
||||
<div
|
||||
data-debug-id="panel-nav-bar"
|
||||
data-debug-label="PanelNavBar — shared/panel-nav-bar.tsx"
|
||||
{...debugProps('panel-nav-bar')}
|
||||
className={cn('flex items-center justify-between px-4 py-2 border-b border-border bg-surface-container-low shrink-0', className)}
|
||||
>
|
||||
<span className="text-sm font-medium text-foreground">{title}</span>
|
||||
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
||||
<span data-debug-id="panel-nav-bar__title" className="text-sm font-medium text-foreground">{title}</span>
|
||||
{actions && <div data-debug-id="panel-nav-bar__actions" className="flex items-center gap-2">{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { PbiStatusApi } from '@/lib/task-status'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
export const PBI_STATUS_LABELS: Record<PbiStatusApi, string> = {
|
||||
ready: 'Klaar voor sprint',
|
||||
|
|
@ -26,7 +27,7 @@ interface PbiStatusSelectProps {
|
|||
|
||||
export function PbiStatusSelect({ value, onChange, className }: PbiStatusSelectProps) {
|
||||
return (
|
||||
<span data-debug-id="pbi-status-select" data-debug-label="PbiStatusSelect — shared/pbi-status-select.tsx">
|
||||
<span {...debugProps('pbi-status-select')}>
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={(v) => { if (v) onChange(v as PbiStatusApi) }}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
export const PRIORITY_LABELS: Record<number, string> = {
|
||||
1: 'Kritiek',
|
||||
|
|
@ -25,7 +26,7 @@ interface PrioritySelectProps {
|
|||
|
||||
export function PrioritySelect({ value, onChange, className }: PrioritySelectProps) {
|
||||
return (
|
||||
<span data-debug-id="priority-select" data-debug-label="PrioritySelect — shared/priority-select.tsx">
|
||||
<span {...debugProps('priority-select')}>
|
||||
<Select
|
||||
value={String(value)}
|
||||
onValueChange={(v) => { if (v) onChange(parseInt(v)) }}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useEffect } from 'react'
|
||||
import { useProductStore } from '@/stores/product-store'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
export function SetCurrentProduct({ id, name }: { id: string; name: string }) {
|
||||
const { setCurrentProduct, clearCurrentProduct } = useProductStore()
|
||||
|
|
@ -11,5 +12,5 @@ export function SetCurrentProduct({ id, name }: { id: string; name: string }) {
|
|||
return () => clearCurrentProduct()
|
||||
}, [id, name, setCurrentProduct, clearCurrentProduct])
|
||||
|
||||
return <span data-debug-id="set-current-product" data-debug-label="SetCurrentProduct — shared/set-current-product.tsx" hidden />
|
||||
return <span {...debugProps('set-current-product')} hidden />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { cn } from '@/lib/utils'
|
||||
import { setActiveSprintAction } from '@/actions/active-sprint'
|
||||
import type { SprintStatusApi } from '@/lib/task-status'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
type SprintItem = { id: string; code: string; sprint_goal: string; status: SprintStatusApi }
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ export function SprintSwitcher({
|
|||
|
||||
if (sprints.length === 0) {
|
||||
return (
|
||||
<span data-debug-id="sprint-switcher" data-debug-label="SprintSwitcher — shared/sprint-switcher.tsx">
|
||||
<span {...debugProps('sprint-switcher')}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
|
|
@ -85,7 +86,7 @@ export function SprintSwitcher({
|
|||
}
|
||||
|
||||
return (
|
||||
<span data-debug-id="sprint-switcher" data-debug-label="SprintSwitcher — shared/sprint-switcher.tsx">
|
||||
<span {...debugProps('sprint-switcher')}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
disabled={isPending}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { DebugToggle } from './status-bar-debug-toggle'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE
|
||||
? new Date(process.env.NEXT_PUBLIC_BUILD_DATE).toLocaleDateString('nl-NL', {
|
||||
|
|
@ -17,11 +18,10 @@ export function StatusBar() {
|
|||
return (
|
||||
<footer
|
||||
className="shrink-0 border-t border-border bg-surface-container-low h-14 px-4 flex items-center justify-between text-sm text-muted-foreground select-none"
|
||||
data-debug-id="status-bar"
|
||||
data-debug-label="StatusBar — shared/status-bar.tsx"
|
||||
{...debugProps('status-bar')}
|
||||
>
|
||||
<span>© {new Date().getFullYear()} Scrum4Me</span>
|
||||
<span>v{version} · gebouwd op {buildDate}{isDev && <DebugToggle />}</span>
|
||||
<span data-debug-id="status-bar__copyright">© {new Date().getFullYear()} Scrum4Me</span>
|
||||
<span data-debug-id="status-bar__build-info">v{version} · gebouwd op {buildDate}{isDev && <DebugToggle />}</span>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface StoryLogEntry {
|
||||
id: string
|
||||
type: string
|
||||
|
|
@ -35,8 +37,7 @@ export function StoryLog({ logs, repoUrl }: StoryLogProps) {
|
|||
if (logs.length === 0) {
|
||||
return (
|
||||
<p
|
||||
data-debug-id="story-log"
|
||||
data-debug-label="StoryLog — shared/story-log.tsx"
|
||||
{...debugProps('story-log')}
|
||||
className="text-sm text-muted-foreground text-center py-4"
|
||||
>
|
||||
Nog geen activiteit. Gebruik de REST API om logs toe te voegen.
|
||||
|
|
@ -46,8 +47,7 @@ export function StoryLog({ logs, repoUrl }: StoryLogProps) {
|
|||
|
||||
return (
|
||||
<div
|
||||
data-debug-id="story-log"
|
||||
data-debug-label="StoryLog — shared/story-log.tsx"
|
||||
{...debugProps('story-log')}
|
||||
className="space-y-3"
|
||||
>
|
||||
{logs.map(log => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
type AvatarSize = 'xs' | 'sm' | 'md' | 'lg'
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ export function UserAvatar({ userId, username, size = 'md', className }: UserAva
|
|||
const initials = username.slice(0, 2).toUpperCase()
|
||||
|
||||
return (
|
||||
<span data-debug-id="user-avatar" data-debug-label="UserAvatar — shared/user-avatar.tsx">
|
||||
<span {...debugProps('user-avatar')}>
|
||||
<Avatar className={cn(SIZE_CLASSES[size], className)}>
|
||||
<AvatarImage src={`/api/users/${userId}/avatar`} alt={username} />
|
||||
<AvatarFallback className="bg-primary-container text-primary-container-foreground font-medium">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
PRODUCT_OWNER: 'Product Owner',
|
||||
|
|
@ -46,7 +47,7 @@ export function UserMenu({ userId, username, email, roles }: UserMenuProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<span data-debug-id="user-menu" data-debug-label="UserMenu — shared/user-menu.tsx">
|
||||
<span {...debugProps('user-menu')}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className="rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue