diff --git a/docs/AGENTIC_AI_ROADMAP.md b/docs/AGENTIC_AI_ROADMAP.md
index 55da2ec..617a728 100644
--- a/docs/AGENTIC_AI_ROADMAP.md
+++ b/docs/AGENTIC_AI_ROADMAP.md
@@ -17,7 +17,7 @@
| TODO | Priority | Phase | File(s) | Summary |
|------|----------|-------|---------|---------|
| **TODO-001** | medium | 0 | `web/src/app/providers.tsx` | Kill switch maintenance banner — create ``, set React state when `checkKillSwitch()` returns `disabled=true`, disable timer creation buttons |
-| **TODO-002** | medium | 0 | `web/src/app/(app)/settings/page.tsx` (create) | Wire feedback button into settings page (or floating FAB) using `feedbackClient` from `web/src/lib/feedback.ts` |
+| ✅ **TODO-002** | medium | 0 | `web/src/app/settings/page.tsx` | ~~Wire feedback button into settings page using `feedbackClient`~~ — settings feedback form submits through `@bytelyst/feedback-client`; verification blocked locally by missing `GITEA_NPM_TOKEN` for private `@bytelyst/*` install (df670d9) |
| **TODO-003** | medium | 0 | `web/src/components/CreateTimerModal.tsx`, `web/src/components/AlarmOverlay.tsx` | Apply `@bytelyst/accessibility` focus trap + screen reader announcements. Ensure `--cm-*` color tokens meet WCAG AA contrast |
| ✅ **TODO-004** | medium | A.1 | `backend/src/modules/routines/routes.ts` | ~~Clone template~~ — templates now cloned via `crypto.randomUUID()`, original stays reusable (0e7c1ae) |
| ✅ **TODO-005** | high | A.4 | `backend/src/lib/ai-context.ts` | ~~Wire LLM~~ — dual-path: extraction-service → Ollama `/api/generate` (5s timeout, feature-gated) (229ce4f) |
@@ -64,7 +64,7 @@
| `@bytelyst/design-tokens` | ✅ Used | `--cm-*` CSS props in globals.css |
| `@bytelyst/ui` | ✅ Used | Imported in providers.tsx |
| `@bytelyst/kill-switch-client` | ❌ **MISSING** | Every other product has this — critical for prod safety |
-| `@bytelyst/feedback-client` | ❌ **MISSING** | No in-app feedback mechanism |
+| `@bytelyst/feedback-client` | ✅ Used | Settings feedback form submits via shared SDK |
| `@bytelyst/event-store` | ❌ **MISSING** | Domain events not persisted — needed for webhooks + audit |
| `@bytelyst/webhook-dispatch` | ❌ **MISSING** | Backend uses hand-rolled dispatcher instead of shared package |
| `@bytelyst/accessibility` | ❌ **MISSING** | Shared accessibility helpers not consumed |
@@ -124,7 +124,7 @@ These proxy to `chronomind-backend` (port 4011) via `chronomind-client.ts`.
- [x] `web/src/lib/feedback.ts` — integrate `@bytelyst/feedback-client` (commit: f3e14e2)
- `createFeedbackClient({ baseUrl, productId, getAccessToken })`
-- [ ] Add feedback button to settings page (or floating FAB) (commit: )
+- [x] Add feedback button to settings page (or floating FAB) (commit: df670d9; status: implemented, verification blocked locally because `pnpm install` requires `GITEA_NPM_TOKEN` for private `@bytelyst/*` packages)
- TODO-002: Wire feedback button into settings page using `feedbackClient` from `web/src/lib/feedback.ts`
### 0.3 — Accessibility Package (Web)
@@ -642,7 +642,7 @@ Systematic audit against `learning_ai_common_plat/packages/` (58 packages) revea
| Gap | Severity | Fixed In |
|-----|----------|----------|
| No `@bytelyst/kill-switch-client` | **Critical** | Phase 0.1 |
-| No `@bytelyst/feedback-client` | Medium | Phase 0.2 |
+| No settings feedback entry point using `@bytelyst/feedback-client` | Medium | Phase 0.2 — complete (df670d9) |
| No `@bytelyst/accessibility` helpers | Medium | Phase 0.3 |
| No feature flags for new features | **Critical** | Phase 0.4 |
| No telemetry events for new features | Medium | Phase 0.5 |
diff --git a/web/src/app/settings/page.tsx b/web/src/app/settings/page.tsx
index 1a52f98..8803b9b 100644
--- a/web/src/app/settings/page.tsx
+++ b/web/src/app/settings/page.tsx
@@ -38,6 +38,7 @@ export default function SettingsPage() {
const [feedbackText, setFeedbackText] = useState('');
const [feedbackSubmitting, setFeedbackSubmitting] = useState(false);
const [feedbackSuccess, setFeedbackSuccess] = useState(false);
+ const [feedbackError, setFeedbackError] = useState('');
useEffect(() => {
setMounted(true);
@@ -106,6 +107,38 @@ export default function SettingsPage() {
.forEach((t) => removeTimer(t.id));
};
+ const handleFeedbackSubmit = async () => {
+ const body = feedbackText.trim();
+ if (!body) return;
+
+ setFeedbackSubmitting(true);
+ setFeedbackError('');
+ try {
+ await feedbackClient.submitWithScreenshot({
+ type: feedbackType,
+ title: body.slice(0, 100),
+ body,
+ screen: 'settings',
+ appVersion: '0.1.0',
+ platform: 'web',
+ deviceContext: {
+ osVersion: navigator.userAgent,
+ appVersion: '0.1.0',
+ deviceModel: navigator.platform || 'web',
+ screenResolution: `${window.screen.width}x${window.screen.height}`,
+ locale: navigator.language,
+ },
+ });
+ setFeedbackSuccess(true);
+ setFeedbackText('');
+ setTimeout(() => setFeedbackSuccess(false), 4000);
+ } catch {
+ setFeedbackError('Feedback could not be sent right now. Please try again in a moment.');
+ } finally {
+ setFeedbackSubmitting(false);
+ }
+ };
+
return (
@@ -526,8 +559,12 @@ export default function SettingsPage() {
))}