Files
kandev/docs/plans/opencode-actionable-error-links/task-03-render-recovery-link.md
T
2026-08-24 11:09:03 +00:00

77 lines
3.2 KiB
Markdown

---
id: "03-render-recovery-link"
title: "Render recovery links"
status: done
wave: 3
depends_on: ["02-persist-recovery-link"]
plan: "plan.md"
spec: "../../specs/agents/requirements/agent-stall-recovery.md"
---
# Task 03: Render Recovery Links
## Acceptance
- Kanban action messages and persistent last-agent-error notices render a
localized external link when `remediation_url` is present.
- Office failed-session entries render the same link from initial API data and
live `session.state_changed` metadata.
- Browser-side defense-in-depth validation rejects invalid URLs before creating
an `href`; valid links use `target="_blank"` and
`rel="noopener noreferrer"`.
- The existing sanitized message, collapsed technical details, recovery
buttons, keyboard navigation, and mobile chat scroll owner remain intact.
## Verification
```text
cd apps && pnpm --filter @kandev/web test -- --run components/task/chat/messages/action-message.test.tsx lib/session-last-agent-error.test.ts components/task/simple/chat-entries.test.ts lib/remediation-url.test.ts
cd apps/web && pnpm run typecheck && pnpm run i18n:check && pnpm run i18n:ratchet
```
Add focused tests for generic/quota cards, dismissed notices, Office mapping,
invalid metadata, translated labels, and the minimum 44px phone affordance.
## Files likely touched
- `apps/web/lib/session-last-agent-error.ts`
- `apps/web/components/task/chat/messages/action-message.tsx`
- `apps/web/components/task/chat/messages/action-message.test.tsx`
- `apps/web/components/task/chat/message-list-shared.tsx`
- `apps/web/app/office/tasks/[id]/page.tsx`
- `apps/web/app/office/tasks/[id]/types.ts`
- `apps/web/components/task/simple/chat-entries.ts`
- `apps/web/components/task/simple/components/run-error-entry.tsx`
- focused Office/chat tests
- `apps/web/src/locales/en/task.json`
- `apps/web/src/locales/en/chat.json`
- matching pseudo-locale files
## Dependencies and risks
Task 02. The external URL is a user-visible security boundary; keep validation
at the backend and browser edges. Do not render the raw diagnostic message as
HTML or use translated text for control flow.
## Results
Implemented 2026-08-07.
- `lib/remediation-url.ts` mirrors the backend allowlist at the browser edge
(host, scheme, path shape, bounded identifier, no port/query/fragment/
userinfo); `components/task/remediation-link.tsx` renders the localized
`chat:openProviderPage` link with `target="_blank"`, `rel="noopener noreferrer"`
and a 44px phone touch target, and returns null for invalid input.
- Wired into `ActionMessageDetails` (generic + quota recovery cards), the
persistent `LastAgentErrorNotice` (`readLastAgentError` reads
`remediation_url`/`remediationUrl`), and Office `RunErrorEntry` via
`buildRunErrorsFromSessions`; the office page maps `metadata` from the API
and merges live `session.state_changed` metadata.
- Unit tests: validator table, action-message link cases (quota/generic/
invalid), notice link cases, session-last-agent-error parsing, and
`buildRunErrorsFromSessions` preservation.
Verification: targeted vitest run (106 tests) and the full web unit suite
(1236 files / 9669 tests) passed; `typecheck`, `i18n:check`, `i18n:ratchet`
clean (en/pt-pt/zh-cn catalogs + regenerated pseudo).