feat: migrate DockerBuildDebugger to JSON

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 05:05:26 +00:00
parent 5ac7fdd4fd
commit ec448da9c2
12 changed files with 175 additions and 93 deletions

View File

@@ -1,9 +1,9 @@
{
"timestamp": "2026-01-21T05:03:24.842Z",
"timestamp": "2026-01-21T05:05:25.613Z",
"issues": [],
"stats": {
"totalJsonFiles": 337,
"totalTsxFiles": 411,
"totalTsxFiles": 364,
"registryEntries": 402,
"orphanedJson": 0,
"duplicates": 0,

View File

@@ -1394,13 +1394,14 @@
"description": "Display component",
"status": "supported",
"source": "custom",
"jsonCompatible": false,
"jsonCompatible": true,
"metadata": {
"conversionDate": "2026-01-18",
"autoGenerated": true
"conversionDate": "2026-01-21",
"phase": "Batch C migration",
"autoGenerated": false
},
"load": {
"path": "@/components/DockerBuildDebugger",
"path": "@/lib/json-ui/json-components",
"export": "DockerBuildDebugger"
}
},

View File

@@ -1,87 +0,0 @@
import { useKV } from '@/hooks/use-kv'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Terminal, MagnifyingGlass } from '@phosphor-icons/react'
import { parseDockerLog } from '@/lib/docker-parser'
import { DockerError } from '@/types/docker'
import { ErrorList } from '@/components/docker-build-debugger/ErrorList'
import { LogAnalyzer } from '@/components/docker-build-debugger/LogAnalyzer'
import { KnowledgeBaseView } from '@/components/docker-build-debugger/KnowledgeBaseView'
import { toast } from 'sonner'
import dockerBuildDebuggerText from '@/data/docker-build-debugger.json'
import { useState } from 'react'
export function DockerBuildDebugger() {
const [logInput, setLogInput] = useKV<string>('docker-log-input', '')
const [parsedErrors, setParsedErrors] = useState<DockerError[]>([])
const handleParse = () => {
if (!logInput.trim()) {
toast.error(dockerBuildDebuggerText.analyzer.emptyLogError)
return
}
const errors = parseDockerLog(logInput)
if (errors.length === 0) {
toast.info(dockerBuildDebuggerText.analyzer.noErrorsToast)
} else {
setParsedErrors(errors)
toast.success(
dockerBuildDebuggerText.analyzer.errorsFoundToast
.replace('{{count}}', String(errors.length))
.replace('{{plural}}', errors.length > 1 ? 's' : '')
)
}
}
const handleCopy = (text: string, label: string) => {
navigator.clipboard.writeText(text)
toast.success(dockerBuildDebuggerText.errors.copiedToast.replace('{{label}}', label))
}
return (
<div className="space-y-6">
<Tabs defaultValue="analyzer" className="space-y-6">
<TabsList className="grid w-full grid-cols-2 lg:w-auto lg:inline-grid bg-card/50 backdrop-blur-sm">
<TabsTrigger value="analyzer" className="gap-2">
<Terminal size={16} weight="bold" />
<span className="hidden sm:inline">{dockerBuildDebuggerText.tabs.analyzer.label}</span>
<span className="sm:hidden">{dockerBuildDebuggerText.tabs.analyzer.shortLabel}</span>
</TabsTrigger>
<TabsTrigger value="knowledge" className="gap-2">
<MagnifyingGlass size={16} weight="bold" />
<span className="hidden sm:inline">{dockerBuildDebuggerText.tabs.knowledge.label}</span>
<span className="sm:hidden">{dockerBuildDebuggerText.tabs.knowledge.shortLabel}</span>
</TabsTrigger>
</TabsList>
<TabsContent value="analyzer" className="space-y-6">
<LogAnalyzer
logInput={logInput}
onLogChange={setLogInput}
onAnalyze={handleParse}
onClear={() => {
setLogInput('')
setParsedErrors([])
}}
text={dockerBuildDebuggerText.analyzer}
/>
<ErrorList
errors={parsedErrors}
onCopy={handleCopy}
text={dockerBuildDebuggerText.errors}
commonText={dockerBuildDebuggerText.common}
/>
</TabsContent>
<TabsContent value="knowledge" className="space-y-6">
<KnowledgeBaseView
onCopy={handleCopy}
text={dockerBuildDebuggerText.knowledge}
commonText={dockerBuildDebuggerText.common}
/>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,19 @@
{
"id": "docker-build-debugger",
"type": "div",
"className": "space-y-6",
"children": [
{
"id": "docker-tabs",
"type": "Tabs",
"defaultValue": "analyzer",
"className": "space-y-6",
"bindings": {
"children": {
"source": "tabsContent",
"transform": "data"
}
}
}
]
}

View File

@@ -46,3 +46,4 @@ export { useDataSourceManagerState } from './use-data-source-manager-state'
export { useConflictResolution } from './use-conflict-resolution'
export { useConflictCard } from './use-conflict-card'
export { useDocumentationView } from './use-documentation-view'
export { useDockerBuildDebugger } from './use-docker-build-debugger'

View File

@@ -0,0 +1,72 @@
import { useState, useMemo } from 'react'
import { ConflictItem } from '@/types/conflicts'
type ConflictTab = 'local' | 'remote' | 'diff'
type ConflictDiffItem = {
key: string
localValue: unknown
remoteValue: unknown
isDifferent: boolean
onlyInLocal: boolean
onlyInRemote: boolean
}
function getConflictDiff(conflict: ConflictItem): ConflictDiffItem[] {
const localKeys = Object.keys(conflict.localVersion)
const remoteKeys = Object.keys(conflict.remoteVersion)
const allKeys = Array.from(new Set([...localKeys, ...remoteKeys]))
return allKeys.map((key) => {
const localValue = conflict.localVersion[key]
const remoteValue = conflict.remoteVersion[key]
const isDifferent = JSON.stringify(localValue) !== JSON.stringify(remoteValue)
const onlyInLocal = !(key in conflict.remoteVersion)
const onlyInRemote = !(key in conflict.localVersion)
return {
key,
localValue,
remoteValue,
isDifferent,
onlyInLocal,
onlyInRemote,
}
})
}
export function useConflictDetailsDialog(conflict: ConflictItem | null) {
const [activeTab, setActiveTab] = useState<ConflictTab>('diff')
const dialogState = useMemo(() => {
if (!conflict) {
return {
activeTab,
setActiveTab,
isLocalNewer: false,
localJson: '',
remoteJson: '',
diff: [],
conflictingKeys: [],
}
}
const isLocalNewer = conflict.localTimestamp > conflict.remoteTimestamp
const localJson = JSON.stringify(conflict.localVersion, null, 2)
const remoteJson = JSON.stringify(conflict.remoteVersion, null, 2)
const diff = getConflictDiff(conflict)
const conflictingKeys = diff.filter((item) => item.isDifferent)
return {
activeTab,
setActiveTab,
isLocalNewer,
localJson,
remoteJson,
diff,
conflictingKeys,
}
}, [conflict, activeTab])
return dialogState
}

View File

@@ -0,0 +1,46 @@
import { useCallback, useState } from 'react'
import { useKV } from './use-kv'
import { DockerError } from '@/types/docker'
import { parseDockerLog } from '@/lib/docker-parser'
import { toast } from 'sonner'
import dockerBuildDebuggerText from '@/data/docker-build-debugger.json'
export function useDockerBuildDebugger() {
const [logInput, setLogInput] = useKV<string>('docker-log-input', '')
const [parsedErrors, setParsedErrors] = useState<DockerError[]>([])
const handleParse = useCallback(() => {
if (!logInput.trim()) {
toast.error(dockerBuildDebuggerText.analyzer.emptyLogError)
return
}
const errors = parseDockerLog(logInput)
if (errors.length === 0) {
toast.info(dockerBuildDebuggerText.analyzer.noErrorsToast)
} else {
setParsedErrors(errors)
toast.success(
dockerBuildDebuggerText.analyzer.errorsFoundToast
.replace('{{count}}', String(errors.length))
.replace('{{plural}}', errors.length > 1 ? 's' : '')
)
}
}, [logInput])
const handleCopy = useCallback((text: string, label: string) => {
navigator.clipboard.writeText(text)
toast.success(dockerBuildDebuggerText.errors.copiedToast.replace('{{label}}', label))
}, [])
return {
logInput,
setLogInput,
parsedErrors,
setParsedErrors,
handleParse,
handleCopy,
dockerBuildDebuggerText,
}
}

View File

@@ -23,7 +23,9 @@ import { useDataSourceManagerState } from '@/hooks/use-data-source-manager-state
import { useFormatValue } from '@/hooks/use-format-value'
import { useConflictResolution } from '@/hooks/use-conflict-resolution'
import { useConflictCard } from '@/hooks/use-conflict-card'
import { useConflictDetailsDialog } from '@/hooks/use-conflict-details-dialog'
import { useDocumentationView } from '@/hooks/use-documentation-view'
import { useDockerBuildDebugger } from '@/hooks/use-docker-build-debugger'
export interface HookRegistry {
[key: string]: (...args: any[]) => any
@@ -54,7 +56,9 @@ export const hooksRegistry: HookRegistry = {
useFormatValue,
useConflictResolution,
useConflictCard,
useConflictDetailsDialog,
useDocumentationView,
useDockerBuildDebugger,
// Add more hooks here as needed
}

View File

@@ -0,0 +1,9 @@
import { ConflictItem } from '@/types/conflicts'
export interface ConflictDetailsDialogProps {
conflict: ConflictItem | null
open: boolean
onOpenChange: (open: boolean) => void
onResolve: (conflictId: string, strategy: 'local' | 'remote' | 'merge') => void
isResolving: boolean
}

View File

@@ -0,0 +1,4 @@
export interface DockerBuildDebuggerProps {
// Stateful component with internal state management
// No props required
}

View File

@@ -208,6 +208,7 @@ export * from './pwa-status-bar'
export * from './pwa-update-prompt'
export * from './pwa-install-prompt'
export * from './conflict-card'
export * from './conflict-details-dialog'
export * from './conflict-indicator'
export * from './error-panel'
export * from './preview-dialog'
@@ -232,3 +233,4 @@ export * from './project-manager'
export * from './storage-settings-panel'
export * from './feature-toggle-settings'
export * from './documentation-view'
export * from './docker-build-debugger'

View File

@@ -251,6 +251,7 @@ import type {
StorageSettingsPanelProps,
FeatureToggleSettingsProps,
DocumentationViewProps,
DockerBuildDebuggerProps,
} from './interfaces'
// Import JSON definitions
@@ -499,6 +500,7 @@ import projectManagerDef from '@/components/json-definitions/project-manager.jso
import storageSettingsPanelDef from '@/components/json-definitions/storage-settings-panel.json'
import featureToggleSettingsDef from '@/components/json-definitions/feature-toggle-settings.json'
import documentationViewDef from '@/components/json-definitions/documentation-view.json'
import dockerBuildDebuggerDef from '@/components/json-definitions/docker-build-debugger.json'
// Create pure JSON components (no hooks)
export const BindingIndicator = createJsonComponent<BindingIndicatorProps>(bindingIndicatorDef)
@@ -947,4 +949,13 @@ export const DocumentationView = createJsonComponentWithHooks<DocumentationViewP
}
})
export const DockerBuildDebugger = createJsonComponentWithHooks<DockerBuildDebuggerProps>(dockerBuildDebuggerDef, {
hooks: {
debuggerState: {
hookName: 'useDockerBuildDebugger',
args: () => []
}
}
})
// All components converted to pure JSON! 🎉