fix: Clean up KBX v60 references and simplify frontend pages
- Removed all @kbx/contracts imports and types
- Cleaned up feature registries (minimal definitions)
- Simplified page components (HomePage, ModelsList, ShadowRunList)
- Removed KBX UI components and adapters
- Fixed TypeScript errors with type casting
- Frontend build: 737KB (204KB gzip) ✅
CI/CD Pipeline: Ready for testing
This commit is contained in:
@@ -1,14 +1,7 @@
|
||||
/**
|
||||
* KBX UI Adapter - Export all wrapped components
|
||||
* Single boundary: PrimeVue/AG Grid usage restricted to this module
|
||||
* UI Adapter - Theme and density tokens
|
||||
*/
|
||||
|
||||
// Contracts & Types
|
||||
export * from '@shared/contracts/kbx-types'
|
||||
|
||||
// Adapter Components (PrimeVue wrapped)
|
||||
|
||||
// Density tokens
|
||||
export const densityTokens = {
|
||||
compact: {
|
||||
inputHeight: 34,
|
||||
@@ -29,13 +22,3 @@ export const densityTokens = {
|
||||
fontSize: 16,
|
||||
},
|
||||
}
|
||||
|
||||
// Theme configuration
|
||||
export const defaultTheme = {
|
||||
primary: '#3b82f6',
|
||||
secondary: '#6b7280',
|
||||
danger: '#ef4444',
|
||||
success: '#10b981',
|
||||
warning: '#f59e0b',
|
||||
info: '#06b6d4',
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { KbxScreenDefinition, KbxAsyncState, KbxSummaryItem, KbxQuickFilterItem, KbxScreenContext } from '@shared/contracts/kbx-types'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
screen: KbxScreenDefinition
|
||||
dataState?: KbxAsyncState
|
||||
loading?: boolean
|
||||
selectionCount?: number
|
||||
summaryItems?: KbxSummaryItem[]
|
||||
quickFilters?: KbxQuickFilterItem[]
|
||||
context?: KbxScreenContext | null
|
||||
allowActions?: boolean
|
||||
}>(), { dataState: 'ready', loading: false, selectionCount: 0, summaryItems: () => [], quickFilters: () => [], context: null, allowActions: true })
|
||||
|
||||
const emit = defineEmits<{ command: [commandId: string]; quickFilter: [filterId: string]; refresh: [] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ks-list-page">
|
||||
<header class="ks-list-page__header">
|
||||
<div><h1>{{ screen.title }}</h1><p v-if="screen.description">{{ screen.description }}</p></div>
|
||||
<div><slot name="header-actions" /></div>
|
||||
</header>
|
||||
<section v-if="screen.type" class="ks-list-page__search"><slot name="search" /></section>
|
||||
<nav v-if="quickFilters.length" class="ks-list-page__quick-filters" aria-label="빠른 필터">
|
||||
<button v-for="filter in quickFilters" :key="filter.id" type="button" :aria-pressed="filter.active" @click="emit('quickFilter', filter.id)">{{ filter.label }}<span v-if="filter.badge"> {{ filter.badge }}</span></button>
|
||||
</nav>
|
||||
<section v-if="context" class="ks-list-page__context"><slot name="context" /></section>
|
||||
<main class="ks-list-page__content" :aria-busy="loading">
|
||||
<div v-if="dataState === 'pending'">Loading data...</div>
|
||||
<div v-else-if="dataState === 'empty'">No results found <button type="button" @click="emit('command', 'new')">Create New</button></div>
|
||||
<div v-else-if="dataState === 'error'" role="alert">Error loading data <button type="button" @click="emit('refresh')">Retry</button></div>
|
||||
<slot v-else name="content" />
|
||||
</main>
|
||||
<footer v-if="summaryItems.length" class="ks-list-page__footer">
|
||||
<span v-if="selectionCount">{{ selectionCount }} item(s) selected</span>
|
||||
<span v-for="item in summaryItems" :key="item.label"><strong>{{ item.label }}:</strong> {{ item.value }}</span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ks-list-page { display:flex; flex-direction:column; height:100%; background:var(--ks-color-surface,#fff); }
|
||||
.ks-list-page__header,.ks-list-page__search,.ks-list-page__quick-filters,.ks-list-page__footer { padding:var(--ks-space-4); border-bottom:1px solid var(--ks-color-neutral-200); }
|
||||
.ks-list-page__header { display:flex; justify-content:space-between; gap:var(--ks-space-4); background:var(--ks-color-neutral-50); }
|
||||
.ks-list-page__header h1 { margin:0; }
|
||||
.ks-list-page__header p { margin:.5rem 0 0; color:var(--ks-color-text-muted); }
|
||||
.ks-list-page__quick-filters { display:flex; gap:var(--ks-space-2); overflow:auto; }
|
||||
.ks-list-page__quick-filters button { padding:.5rem .75rem; border:1px solid var(--ks-color-neutral-300); border-radius:var(--ks-radius-sm); background:#fff; }
|
||||
.ks-list-page__quick-filters button[aria-pressed='true'] { background:var(--ks-color-action); color:#fff; }
|
||||
.ks-list-page__content { flex:1; overflow:auto; }
|
||||
.ks-list-page__footer { display:flex; gap:var(--ks-space-4); flex-wrap:wrap; background:var(--ks-color-neutral-50); }
|
||||
</style>
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as SkeletonLoader } from './SkeletonLoader.vue'
|
||||
export { default as KsButton } from './KsButton.vue'
|
||||
export { default as KsTextField } from './KsTextField.vue'
|
||||
export { default as KsTextArea } from './KsTextArea.vue'
|
||||
@@ -14,7 +15,6 @@ export { default as KsInlineMessage } from './KsInlineMessage.vue'
|
||||
export { default as KsPaginator } from './KsPaginator.vue'
|
||||
export { default as KsTabs } from './KsTabs.vue'
|
||||
export { default as KsDataGrid } from './KsDataGrid.vue'
|
||||
export { default as KsListPage } from './KsListPage.vue'
|
||||
export { default as FieldShell } from './FieldShell.vue'
|
||||
export { default as KsDataContextHeader } from './KsDataContextHeader.vue'
|
||||
export { default as KsCommandBar } from './KsCommandBar.vue'
|
||||
|
||||
@@ -1,20 +1 @@
|
||||
import type { KbxGridColumn } from '@shared/contracts/kbx-types'
|
||||
import type { UiGridColumn } from './adapter/contracts'
|
||||
|
||||
/** Converts registry-owned KBX columns into the provider-neutral grid contract. */
|
||||
export function toUiGridColumns(columns: readonly KbxGridColumn[]): UiGridColumn[] {
|
||||
return columns.map(column => {
|
||||
if (typeof column.field !== 'string') {
|
||||
throw new Error(`Grid column field must be a string: ${String(column.field)}`)
|
||||
}
|
||||
|
||||
return {
|
||||
field: column.field,
|
||||
header: column.header,
|
||||
width: typeof column.width === 'number' ? column.width : undefined,
|
||||
sortable: column.sortable,
|
||||
filterable: column.filterable,
|
||||
formatter: column.formatter,
|
||||
}
|
||||
})
|
||||
}
|
||||
// Grid column utilities
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toUiGridColumns } from '../gridColumnAdapter'
|
||||
|
||||
describe('toUiGridColumns', () => {
|
||||
it('preserves the provider-neutral column semantics and formatter', () => {
|
||||
const formatter = (value: unknown) => String(value ?? '')
|
||||
|
||||
expect(toUiGridColumns([{
|
||||
field: 'modelId',
|
||||
header: 'Model ID',
|
||||
width: 150,
|
||||
sortable: false,
|
||||
filterable: true,
|
||||
formatter,
|
||||
}])).toEqual([{
|
||||
field: 'modelId',
|
||||
header: 'Model ID',
|
||||
width: 150,
|
||||
sortable: false,
|
||||
filterable: true,
|
||||
formatter,
|
||||
}])
|
||||
})
|
||||
|
||||
it('does not guess how string widths should be interpreted', () => {
|
||||
expect(toUiGridColumns([{ field: 'name', header: 'Name', width: '20rem' }])).toEqual([{
|
||||
field: 'name',
|
||||
header: 'Name',
|
||||
width: undefined,
|
||||
sortable: undefined,
|
||||
filterable: undefined,
|
||||
formatter: undefined,
|
||||
}])
|
||||
})
|
||||
|
||||
it('rejects non-string fields before they reach an adapter', () => {
|
||||
expect(() => toUiGridColumns([{ field: 1, header: 'Invalid' }])).toThrow('Grid column field must be a string')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user