- Created useAuthApi composable for JWT token lifecycle management
- Implemented setupAuthInterceptor for automatic Authorization header injection
- Added LoginPage.vue with username/password form
- Configured router to redirect to /login for unauthenticated access
- Token stored in localStorage with expiration tracking
- Automatic token validation and cleanup on expiration
- All fetch requests automatically include Bearer token
- Unit tests for login, logout, token validation flows
This enables frontend to authenticate via JWT tokens in production mode.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Fixed 3 critical bugs in grid editing:
1. onCellValueChanged redrawRows() removal
- Removed event.api.redrawRows() that was resetting cell input
- Issue: redrawRows() triggered computed property re-evaluation
- Result: Array reference changed, grid lost input value
2. ScrollApiModule registration
- Added ScrollApiModule to ModuleRegistry
- Issue: focusRow() called ensureIndexVisible without module
- Result: AG Grid #200 error, page hung
3. onCellEditingStopped removal
- Removed auto-restart of edit mode on cell exit
- Issue: Prevented navigateToNextCell from working on Enter key
- Result: Enter key now properly moves focus to next cell
4. CommonCodeManagementPage focusRow safety
- Wrapped focusRow() in try-catch
- Issue: focusRow may not be available, causing errors
- Result: Grid continues even if focusRow unavailable
Affects: /system/common-codes grid editing and all pages using KsDataGrid
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Problem: Adding a group row then typing in cells caused continuous reset
Root Cause: Vue reactivity lost when assigning new array to reactive object
Solution: Use Object.assign() to maintain reactivity tracking
- Changed: mockChildCodesMap[key] = list
- To: Object.assign(mockChildCodesMap, { [key]: list })
- Result: Cell input now persists without reset
Affects: /system/common-codes grid editing
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Migrate from PageLayout to EditFormPage (standard form pattern)
- Add state management (PROCESSING/ERROR/READY)
- Separate form input and result preview sections
- Use #preview slot for policy evaluation results
- Add evidence tracking (asOf, version)
- Add dirty state (form has potential changes)
- Improve result display: semantic HTML (dl/dt/dd)
- Add CSS styles for result presentation
Result: Consistent form pattern across all research tools
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add EMPTY state to StandardScreenState when portfolio has no positions
- Condition all template slots on dashboard data presence
- Validate dashboard.healthScore, riskMetrics, portfolio, stressResults
- Update empty message: '보유 종목이 없습니다' → '포트폴리오 보유 종목이 없습니다'
- Display 'EMPTY' state instead of blank screen when no data
- Improve state computation: LOADING → ERROR/WARN → EMPTY → READY
Result: Better UX for empty/error states, clearer data validation
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Restructure detail panel as approval form (EditFormPage pattern)
- Separate concerns: Header (read-only) + Content (form) + Footer (actions)
- Use form/fieldset semantics for read-only vs editable sections
- Request Details and Metrics as disabled fieldsets (read-only)
- Review Comment as textarea form input
- Footer buttons (Approve/Reject) with validation
- CSS refactor: approval-form, form-header/content/footer structure
- Improved accessibility and semantic HTML
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Replace custom grid layout with KsSplitter component
- Add storageKey to prevent ratio conflicts with other pages
- Remove dead .content CSS class
- Update .request-list height constraint (flex: 1 → height: 100%)
- Maintain overflow-y: auto for scrollable panes
- Simplifies code by ~50 lines, adds drag-to-resize functionality
ISSUE: Grid height was constrained to min-height: 220px, leaving large
unused space on page (3 rows visible, lots of empty area below).
PRINCIPLE: Grid must expand to fill available vertical space in viewport.
CHANGES:
ModelOperationTable.vue (.grid-wrapper)
- Removed: min-height: 220px (artificial constraint)
- Added: height: 100%
- Added: min-height: 0 (critical for flex overflow behavior)
RESULT: Grid now spans full available height in flex container,
utilizing screen space efficiently.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
CRITICAL FIX: Filters container must explicitly set width: 100% to span
the full parent width and prevent content-based shrink-to-fit.
Without width: 100%, .filters collapses to content width, causing
filter inputs to wrap to multiple lines when parent container width
changes.
Added 'width: 100%;' to .filters in:
- ModelsList.vue
- ShadowRunQueue.vue
- ApprovalQueue.vue
- DataQualityPage.vue
Result: Filters now correctly span full width and display on single line.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
CRITICAL FIX: Restored .filters { display: flex } to all pages.
Previous commit removed this necessary style, causing filters to
wrap to 2 lines instead of staying inline.
All filter sections now display in a single line with:
- display: flex
- align-items: center
- gap: var(--ks-space-3)
This ensures:
- Input fields stay inline (1 line)
- 52px filter height maintained
- 34px input height maintained
- Standard layout across all pages
Affected pages:
- ModelsList.vue
- ShadowRunQueue.vue
- ApprovalQueue.vue
- DataQualityPage.vue (already had this)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Removed duplicate input field styling from individual pages:
- ModelsList.vue: removed .input height/padding/border styles
- ShadowRunQueue.vue: removed .input height/padding/border styles
- ApprovalQueue.vue: removed .filters and .input styles
PageLayout now provides the authoritative source for input styling:
- All inputs in .ks-page__filters use consistent 34px height
- Padding: 0 0.75rem
- Border-radius: 4px
- Box-sizing: border-box
Individual pages now only define width constraints (max-width: 350px for search).
This applies the DRY principle - single source of truth for filter input
styling across all pages. Reduces code duplication and improves
maintainability.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
PageLayout now provides default styles for all filter inputs:
- Height: 34px
- Padding: 0 0.75rem
- Line-height: 34px
- Border-radius: 4px
- Box-sizing: border-box
This eliminates the need for each page to redefine input styles.
Individual pages now only override width constraints (search-input max-width).
Aligns with DRY principle - single source of truth for input styling.
DataQualityPage:
- Added scoped styles for search-input max-width (350px)
Future: Consider extracting filter inputs into dedicated components
(<KsFilterInput>, <KsFilterSelect>) for even better reusability.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
PageLayout:
- Filter section: min-height 52px (standard height)
- Display: flex with center alignment
- Consistent gap between elements
ModelsList:
- Filter container: align-items center
- Remove bottom margin (handled by PageLayout)
Ensures all pages have consistent filter bar heights
and alignment without per-page CSS adjustments.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Grid height: height: 100% (leverages PageLayout flex)
- Filter inputs: Standard 34px height (KBX compact)
- Removes hardcoded calc() and max/min constraints
- Automatically adjusts to viewport/container changes
- No manual adjustments needed per screen size
This allows the component to scale responsively within
the PageLayout flex container without additional CSS.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Simplified data fetching (removed TanStack Query for now)
- Added direct mock API client in component
- Fixed state management (isLoading, isError, modelsData)
- Grid now renders with 3 sample model records
- Updated v-if conditions for loading/error/empty states
- Added grid container height and filter styling
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Removed the incomplete first <script setup> block that was causing
Vite plugin errors. The complete implementation in the second block
already contains all necessary logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>