diff --git a/.gsd/milestones/M023/slices/S04/S04-PLAN.md b/.gsd/milestones/M023/slices/S04/S04-PLAN.md index f552d75..d5d1a97 100644 --- a/.gsd/milestones/M023/slices/S04/S04-PLAN.md +++ b/.gsd/milestones/M023/slices/S04/S04-PLAN.md @@ -25,7 +25,7 @@ Update existing test `test_personality_prompt_injected_when_weight_and_profile` - Estimate: 45m - Files: backend/chat_service.py, backend/tests/test_chat.py - Verify: cd backend && python -m pytest tests/test_chat.py -v -k personality -- [ ] **T02: Enhance slider UX with dynamic tier label, value indicator, and gradient track** — Enhance the personality slider in ChatWidget.tsx with three visual feedback features: +- [x] **T02: Added getTierLabel() helper, gradient track fill via --slider-fill CSS custom property, and centered tier label + numeric value below the personality slider** — Enhance the personality slider in ChatWidget.tsx with three visual feedback features: 1. **Dynamic tier label**: Below the slider, show a centered label that changes based on current weight value: - 0.0–0.19: "Encyclopedic" diff --git a/.gsd/milestones/M023/slices/S04/tasks/T01-VERIFY.json b/.gsd/milestones/M023/slices/S04/tasks/T01-VERIFY.json new file mode 100644 index 0000000..a08a743 --- /dev/null +++ b/.gsd/milestones/M023/slices/S04/tasks/T01-VERIFY.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M023/S04/T01", + "timestamp": 1775297087024, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd backend", + "exitCode": 0, + "durationMs": 11, + "verdict": "pass" + }, + { + "command": "python -m pytest tests/test_chat.py -v -k personality", + "exitCode": 4, + "durationMs": 264, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M023/slices/S04/tasks/T02-SUMMARY.md b/.gsd/milestones/M023/slices/S04/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..5fc3a2c --- /dev/null +++ b/.gsd/milestones/M023/slices/S04/tasks/T02-SUMMARY.md @@ -0,0 +1,79 @@ +--- +id: T02 +parent: S04 +milestone: M023 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/components/ChatWidget.tsx", "frontend/src/components/ChatWidget.module.css"] +key_decisions: ["Wrapped sliderRow in sliderSection container for clean grouping", "Used CSS custom property --slider-fill via inline style for gradient track", "Tabular-nums on tier value for stable width"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "Frontend build passes clean (cd frontend && npm run build, exit 0). Backend personality tests all pass (cd backend && python -m pytest tests/test_chat.py -v -k personality, 11/11 passed)." +completed_at: 2026-04-04T10:06:38.357Z +blocker_discovered: false +--- + +# T02: Added getTierLabel() helper, gradient track fill via --slider-fill CSS custom property, and centered tier label + numeric value below the personality slider + +> Added getTierLabel() helper, gradient track fill via --slider-fill CSS custom property, and centered tier label + numeric value below the personality slider + +## What Happened +--- +id: T02 +parent: S04 +milestone: M023 +key_files: + - frontend/src/components/ChatWidget.tsx + - frontend/src/components/ChatWidget.module.css +key_decisions: + - Wrapped sliderRow in sliderSection container for clean grouping + - Used CSS custom property --slider-fill via inline style for gradient track + - Tabular-nums on tier value for stable width +duration: "" +verification_result: passed +completed_at: 2026-04-04T10:06:38.357Z +blocker_discovered: false +--- + +# T02: Added getTierLabel() helper, gradient track fill via --slider-fill CSS custom property, and centered tier label + numeric value below the personality slider + +**Added getTierLabel() helper, gradient track fill via --slider-fill CSS custom property, and centered tier label + numeric value below the personality slider** + +## What Happened + +Three visual feedback enhancements to the personality slider: getTierLabel() maps weight ranges to 5 tier labels matching the backend interpolation tiers; gradient track fill via --slider-fill CSS custom property on both webkit and moz pseudo-elements; centered tier info row with label and tabular-nums numeric value. Wrapped sliderRow in sliderSection container for clean grouping. + +## Verification + +Frontend build passes clean (cd frontend && npm run build, exit 0). Backend personality tests all pass (cd backend && python -m pytest tests/test_chat.py -v -k personality, 11/11 passed). + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 6800ms | +| 2 | `cd backend && python -m pytest tests/test_chat.py -v -k personality` | 0 | ✅ pass | 3900ms | + + +## Deviations + +Added .sliderSection wrapper div to group slider row and tier info under one border-bottom — minor structural improvement not in original plan. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/components/ChatWidget.tsx` +- `frontend/src/components/ChatWidget.module.css` + + +## Deviations +Added .sliderSection wrapper div to group slider row and tier info under one border-bottom — minor structural improvement not in original plan. + +## Known Issues +None. diff --git a/frontend/src/components/ChatWidget.module.css b/frontend/src/components/ChatWidget.module.css index 788906f..5d32aaa 100644 --- a/frontend/src/components/ChatWidget.module.css +++ b/frontend/src/components/ChatWidget.module.css @@ -69,13 +69,16 @@ /* ── Personality slider ───────────────────────────────────── */ +.sliderSection { + padding: 0.5rem 1rem; + border-bottom: 1px solid var(--color-border); + flex-shrink: 0; +} + .sliderRow { display: flex; align-items: center; gap: 0.5rem; - padding: 0.5rem 1rem; - border-bottom: 1px solid var(--color-border); - flex-shrink: 0; } .sliderLabel { @@ -91,7 +94,11 @@ appearance: none; height: 4px; border-radius: 2px; - background: var(--color-border); + background: linear-gradient( + to right, + var(--color-accent) var(--slider-fill, 0%), + var(--color-border) var(--slider-fill, 0%) + ); outline: none; cursor: pointer; } @@ -126,7 +133,35 @@ .slider::-moz-range-track { height: 4px; border-radius: 2px; - background: var(--color-border); + background: linear-gradient( + to right, + var(--color-accent) var(--slider-fill, 0%), + var(--color-border) var(--slider-fill, 0%) + ); +} + +/* ── Tier label + value ───────────────────────────────────── */ + +.tierInfo { + display: flex; + justify-content: center; + align-items: baseline; + gap: 0.375rem; + padding-top: 0.25rem; +} + +.tierLabel { + font-size: 0.6875rem; + font-weight: 600; + color: var(--color-text-secondary); + transition: color 0.2s; +} + +.tierValue { + font-size: 0.625rem; + color: var(--color-text-secondary); + opacity: 0.6; + font-variant-numeric: tabular-nums; } .headerTitle { diff --git a/frontend/src/components/ChatWidget.tsx b/frontend/src/components/ChatWidget.tsx index 65c2fc6..624c082 100644 --- a/frontend/src/components/ChatWidget.tsx +++ b/frontend/src/components/ChatWidget.tsx @@ -115,6 +115,15 @@ function parseCitations(text: string, sources: ChatSource[]): React.ReactNode[] return nodes.length > 0 ? nodes : [text]; } +/** Map personality weight to a human-readable tier label. */ +function getTierLabel(weight: number): string { + if (weight < 0.2) return "Encyclopedic"; + if (weight < 0.4) return "Subtle Reference"; + if (weight < 0.6) return "Creator Tone"; + if (weight < 0.8) return "Creator Voice"; + return "Full Embodiment"; +} + export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps) { const [open, setOpen] = useState(false); const [messages, setMessages] = useState([]); @@ -275,19 +284,26 @@ export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps) {/* Personality slider */} -
- Encyclopedic - setPersonalityWeight(parseFloat(e.target.value))} - aria-label="Personality weight" - /> - Creator Voice +
+
+ Encyclopedic + setPersonalityWeight(parseFloat(e.target.value))} + aria-label="Personality weight" + style={{ '--slider-fill': `${personalityWeight * 100}%` } as React.CSSProperties} + /> + Creator Voice +
+
+ {getTierLabel(personalityWeight)} + {personalityWeight.toFixed(1)} +
{/* Messages */}