2026-03-15 12:35:17 +00:00
---
name : polish-forms
description : Improve form validation across Archipelago UI with real-time feedback, input sanitization, disabled states during submission, and consistent error messaging. Use when user says "polish forms", "form validation", "input validation", or "fix forms".
---
2026-03-08 08:06:52 +00:00
# Skill: Polish Form Validation
Improve all form inputs to have real-time validation feedback, proper trimming, disabled states during submission, and consistent error messaging.
## Forms to Polish
### 1. Login.vue — Password Setup
- Real-time validation as user types (debounced 300ms):
- Length >= 8 chars (show checkmark/X)
- Passwords match (show match indicator)
- Trim input on submit
- Disable submit button while `isSubmitting`
- Clear error on new input
### 2. Login.vue — TOTP Verification
- `inputmode="numeric"` + `pattern="[0-9]*"`
- Auto-submit when 6 digits entered
- Show session timeout countdown if applicable
- Trim and strip non-numeric characters on paste
### 3. Settings.vue — Password Change
- Real-time strength validation:
- 12+ characters
- Has uppercase, lowercase, digit, special char
- New password matches confirmation
- Show strength meter (weak/medium/strong)
- Disable button during submission
- Show spinner in button during async operation
### 4. Any other form inputs found across views
## Validation Pattern
```typescript
const password = ref ( '' )
const confirmPassword = ref ( '' )
const isSubmitting = ref ( false )
const passwordErrors = computed (() => {
const errors : string [] = []
if ( password . value . length > 0 && password . value . length < 8 )
errors . push ( 'Must be at least 8 characters' )
return errors
})
const passwordsMatch = computed (() =>
confirmPassword . value . length > 0 && password . value === confirmPassword . value
)
async function submit() {
if ( isSubmitting . value ) return
isSubmitting . value = true
try {
await rpcClient . call (...)
} catch ( err ) {
errorMessage . value = formatError ( err )
} finally {
isSubmitting . value = false
}
}
```
## Template Pattern
```vue
< input v-model = "password" type="password" class="glass-input" />
< ul v-if = "passwordErrors.length" class="text-red-400 text-xs mt-1 space-y-0.5" >
< li v-for = "err in passwordErrors" :key="err" >{{ err }}</ li >
</ ul >
< button
class = "glass-button"
: disabled = "isSubmitting || passwordErrors.length > 0"
@click ="submit"
>
< span v-if = "isSubmitting" > Saving... </ span >
< span v-else > Save </ span >
</ button >
```
## Input Trimming
All text inputs should be trimmed before submission:
```typescript
const trimmed = password . value . trim ()
```
## Error Message Consistency
Create or use a `formatError` utility:
```typescript
function formatError ( err : unknown ) : string {
if ( err instanceof Error ) {
if ( err . message . includes ( 'fetch' ) || err . message . includes ( 'network' ))
return 'Unable to reach server. Check your connection.'
if ( err . message . includes ( '401' ) || err . message . includes ( 'unauthorized' ))
return 'Session expired. Please log in again.'
return err . message
}
return 'Something went wrong. Please try again.'
}
```
## Verification
For each form:
- [ ] Real-time validation shows feedback as user types
- [ ] Submit button disabled during operation
- [ ] Submit button disabled when validation fails
- [ ] Inputs trimmed before submission
- [ ] Error messages are user-friendly (no raw error strings)
- [ ] Success feedback shown after completion
## Deploy After Fixes
```bash
./scripts/deploy-to-target.sh --live
```
Test each form with: valid input, invalid input, empty input, whitespace-only input, rapid double-click on submit.