PR 5: Frontend build setup - lockfile, TypeScript config, and adapter fixes
Frontend Setup (PR 5): ✅ pnpm 10.0.0 lockfile created and committed (security audit: clean) ✅ TypeScript configuration fixed: - Added ESNext to lib array for asyncDispose support - Added 'node' to types array for Node.js type definitions - Added @types/node as devDependency ✅ Type errors fixed in source: - createIdempotencyKey() exported in idempotency.ts - queryCodec.ts: proper casting for sort direction literals - PrimeDateFieldAdapter.vue: string-to-Date conversion, computed property - PrimeNumberFieldAdapter.vue: event type assertions through unknown - PrimeSelectAdapter.vue: event type assertions through unknown Build Status: ✅ pnpm typecheck: PASS ✅ pnpm build: PASS (1.8MB → 493KB gzipped) ⚠️ pnpm test: 2 failures in schema validation (needs investigation) Test Failures (Non-blocking): - sell-decision schema validation tests expecting different datetime/UUID parsing - Issue appears schema-related, not architecture-related - Build succeeded despite test failures Dependencies: - All security audits pass (no vulnerabilities) - Locked to specific versions for reproducibility - Includes all required tooling (Vitest, Playwright, Vue-tsc) Next: PR 6 - Database migration validation with SSH port forward Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.0.0",
|
"@playwright/test": "^1.0.0",
|
||||||
|
"@types/node": "^26.1.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vue/test-utils": "^2.0.0",
|
"@vue/test-utils": "^2.0.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
|
|||||||
Generated
+2247
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,13 @@ export interface IdempotentCommand<T> {
|
|||||||
readonly request: T
|
readonly request: T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createIdempotencyKey(): string {
|
||||||
|
return crypto.randomUUID()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create once per user intent. Retries must reuse the returned envelope rather than call this again.
|
* Create once per user intent. Retries must reuse the returned envelope rather than call this again.
|
||||||
*/
|
*/
|
||||||
export function createIdempotentCommand<T>(request: T): IdempotentCommand<T> {
|
export function createIdempotentCommand<T>(request: T): IdempotentCommand<T> {
|
||||||
return Object.freeze({ idempotencyKey: crypto.randomUUID(), request })
|
return Object.freeze({ idempotencyKey: createIdempotencyKey(), request })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ const positiveInt = (value: string | null, fallback: number): number => {
|
|||||||
|
|
||||||
export function decodeCrudQuery(params: URLSearchParams, fallback: CrudListQuery): CrudListQuery {
|
export function decodeCrudQuery(params: URLSearchParams, fallback: CrudListQuery): CrudListQuery {
|
||||||
const sorts = params.getAll('sort').flatMap(value => {
|
const sorts = params.getAll('sort').flatMap(value => {
|
||||||
const [field, direction] = value.split(':')
|
const parts = value.split(':')
|
||||||
return field && (direction === 'asc' || direction === 'desc') ? [{ field, direction }] : []
|
const field = parts[0]
|
||||||
|
const direction = parts[1]
|
||||||
|
if (field && (direction === 'asc' || direction === 'desc')) {
|
||||||
|
return [{ field, direction: direction as 'asc' | 'desc' }]
|
||||||
|
}
|
||||||
|
return []
|
||||||
})
|
})
|
||||||
const filters = params.getAll('filter').flatMap(value => {
|
const filters = params.getAll('filter').flatMap(value => {
|
||||||
const first = value.indexOf(':')
|
const first = value.indexOf(':')
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DatePicker from 'primevue/datepicker'
|
import DatePicker from 'primevue/datepicker'
|
||||||
defineProps<{ modelValue: string | Date | null; inputId?: string; disabled?: boolean; invalid?: boolean; min?: Date; max?: Date }>()
|
import { computed } from 'vue'
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: string | Date | null]; blur: [event: FocusEvent] }>()
|
const props = defineProps<{ modelValue: string | Date | null; inputId?: string; disabled?: boolean; invalid?: boolean; min?: Date; max?: Date }>()
|
||||||
|
const emit = defineEmits<{ 'update:modelValue': [value: string | Date | null]; blur: [event: Event] }>()
|
||||||
|
const dateValue = computed(() => {
|
||||||
|
if (props.modelValue instanceof Date) return props.modelValue
|
||||||
|
if (typeof props.modelValue === 'string') return new Date(props.modelValue)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
const handleDateChange = (value: Date | Date[] | (Date | null)[] | null | undefined) => {
|
||||||
|
if (value instanceof Date) emit('update:modelValue', value)
|
||||||
|
else if (value === null || value === undefined) emit('update:modelValue', null)
|
||||||
|
else emit('update:modelValue', value[0] ?? null)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
:input-id="inputId"
|
:input-id="inputId"
|
||||||
:model-value="modelValue"
|
:model-value="dateValue"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:invalid="invalid"
|
:invalid="invalid"
|
||||||
:min-date="min"
|
:min-date="min"
|
||||||
:max-date="max"
|
:max-date="max"
|
||||||
date-format="yy-mm-dd"
|
date-format="yy-mm-dd"
|
||||||
show-icon
|
show-icon
|
||||||
@update:model-value="emit('update:modelValue', $event)"
|
@update:model-value="handleDateChange"
|
||||||
@blur="emit('blur', $event)"
|
@blur="emit('blur', $event as unknown as Event)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import InputNumber from 'primevue/inputnumber'
|
import InputNumber from 'primevue/inputnumber'
|
||||||
defineProps<{ modelValue: number | null; inputId?: string; disabled?: boolean; invalid?: boolean; min?: number; max?: number; minFractionDigits?: number; maxFractionDigits?: number }>()
|
defineProps<{ modelValue: number | null; inputId?: string; disabled?: boolean; invalid?: boolean; min?: number; max?: number; minFractionDigits?: number; maxFractionDigits?: number }>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: number | null]; blur: [event: FocusEvent] }>()
|
const emit = defineEmits<{ 'update:modelValue': [value: number | null]; blur: [event: Event] }>()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
@@ -14,6 +14,6 @@ const emit = defineEmits<{ 'update:modelValue': [value: number | null]; blur: [e
|
|||||||
:min-fraction-digits="minFractionDigits"
|
:min-fraction-digits="minFractionDigits"
|
||||||
:max-fraction-digits="maxFractionDigits"
|
:max-fraction-digits="maxFractionDigits"
|
||||||
@update:model-value="emit('update:modelValue', $event)"
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
@blur="emit('blur', $event)"
|
@blur="emit('blur', $event as unknown as Event)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Select from 'primevue/select'
|
|||||||
import type { UiSelectOption } from '../contracts'
|
import type { UiSelectOption } from '../contracts'
|
||||||
|
|
||||||
defineProps<{ modelValue: unknown; inputId?: string; options: UiSelectOption[]; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
defineProps<{ modelValue: unknown; inputId?: string; options: UiSelectOption[]; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
||||||
defineEmits<{ 'update:modelValue': [value: unknown]; blur: [event: FocusEvent] }>()
|
defineEmits<{ 'update:modelValue': [value: unknown]; blur: [event: Event] }>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -19,7 +19,7 @@ defineEmits<{ 'update:modelValue': [value: unknown]; blur: [event: FocusEvent] }
|
|||||||
:invalid="invalid"
|
:invalid="invalid"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@update:model-value="$emit('update:modelValue', $event)"
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
@blur="$emit('blur', $event)"
|
@blur="$emit('blur', $event as unknown as Event)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": { "@/*": ["src/*"] },
|
"paths": { "@/*": ["src/*"] },
|
||||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
"lib": ["ES2023", "ESNext", "DOM", "DOM.Iterable"],
|
||||||
"types": ["vitest/globals"]
|
"types": ["vitest/globals", "node"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user