Conversation
There was a problem hiding this comment.
Findings
-
[Major] Disabled-reason tooltip copy bypasses i18n, causing locale regression (zh-CN users will still see English in these newly added tooltips). Evidence:
apps/desktop/src/renderer/src/components/Settings.tsx:375,apps/desktop/src/renderer/src/onboarding/ChooseModel.tsx:98,apps/desktop/src/renderer/src/onboarding/PasteKey.tsx:227,apps/desktop/src/renderer/src/onboarding/Welcome.tsx:37
Suggested fix:const t = useT(); <Tooltip label={disabled ? t('disabledReason.enterApiKeyToValidate') : undefined} side="top" > ... </Tooltip>
-
[Minor] The new
Tooltipearly-return branch has no automated coverage, so regressions (rendering wrappers when label is undefined / changed hover behavior) may slip through. Evidence:packages/ui/src/components/Tooltip.tsx:15
Suggested fix:it('renders children without tooltip chrome when label is undefined', () => { render(<Tooltip label={undefined}><button>Send</button></Tooltip>); expect(screen.getByRole('button', { name: 'Send' })).toBeInTheDocument(); expect(screen.queryByRole('tooltip')).toBeNull(); });
Summary
- Review mode: initial
- Found 2 issues in the latest head diff, including one localization regression tied to newly added disabled-state tooltip text.
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
| <Tooltip | ||
| label={ | ||
| form.apiKey.trim().length === 0 || form.validating | ||
| ? 'Enter an API key to validate' |
There was a problem hiding this comment.
[Major] This new tooltip reason is hardcoded English. The PR also adds disabledReason.* locale keys, so this bypass causes zh-CN regression.
Suggested fix:
const t = useT();
label={form.apiKey.trim().length === 0 || form.validating
? t('disabledReason.enterApiKeyToValidate')
: undefined}| }; | ||
|
|
||
| export function Tooltip({ label, side = 'bottom', children }: TooltipProps) { | ||
| if (!label) return <>{children}</>; |
There was a problem hiding this comment.
[Minor] New behavior branch (if (!label) return children) should have a unit test to prevent regressions in wrapper/hover semantics.
Suggested fix:
it('renders children without tooltip when label is undefined', () => {
render(<Tooltip label={undefined}><button>Send</button></Tooltip>);
expect(screen.queryByRole('tooltip')).toBeNull();
});There was a problem hiding this comment.
Findings
- [Minor] New
Tooltipfallback branch has no unit coverage, so regressions in wrapper/hover behavior can slip in unnoticed across all newly wrapped disabled-button states. Evidencepackages/ui/src/components/Tooltip.tsx:15
Suggested fix:import { render, screen } from '@testing-library/react'; import { Tooltip } from './Tooltip'; it('renders children without tooltip when label is undefined', () => { render( <Tooltip label={undefined}> <button type="button">Send</button> </Tooltip>, ); expect(screen.getByRole('button', { name: 'Send' })).toBeInTheDocument(); expect(screen.queryByRole('tooltip')).toBeNull(); });
Summary
- Review mode: follow-up after new commits
- Found 1 issue in the latest head diff.
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
| }; | ||
|
|
||
| export function Tooltip({ label, side = 'bottom', children }: TooltipProps) { | ||
| if (!label) return <>{children}</>; |
There was a problem hiding this comment.
[Minor] This new branch changes rendering behavior (children only when label is absent) but has no unit test coverage.
Suggested fix:
it('renders children without tooltip when label is undefined', () => {
render(
<Tooltip label={undefined}>
<button type="button">Send</button>
</Tooltip>,
);
expect(screen.queryByRole('tooltip')).toBeNull();
});There was a problem hiding this comment.
Findings
- [Major] Disabled tooltip reasons are not keyboard-accessible, so the new disabled-state guidance is effectively mouse-only. The tooltip is shown only via
group-hover/group-focus-within, but its wrapper is a plainspanthat cannot receive focus. Evidencepackages/ui/src/components/Tooltip.tsx:17,packages/ui/src/components/Tooltip.tsx:21.
Suggested fix:import { useId, type ReactNode } from 'react'; export function Tooltip({ label, side = 'bottom', children }: TooltipProps) { const tooltipId = useId(); if (!label) return <>{children}</>; return ( <span className="relative inline-flex group" tabIndex={0} aria-describedby={tooltipId} > {children} <span id={tooltipId} role="tooltip" className={`... group-hover:opacity-100 group-focus-within:opacity-100 ...`}> {label} </span> </span> ); }
Summary
- Review mode: follow-up after new commits
- Found 1 issue in the latest head diff.
docs/VISION.md: Not found in repo/docs.docs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
| export function Tooltip({ label, side = 'bottom', children }: TooltipProps) { | ||
| if (!label) return <>{children}</>; | ||
| return ( | ||
| <span className="relative inline-flex group"> |
There was a problem hiding this comment.
[Major] This wrapper is not focusable, so disabled-state tooltip reasons are mouse-only despite group-focus-within styles. Suggested fix:
import { useId, type ReactNode } from "react";
export function Tooltip({ label, side = "bottom", children }: TooltipProps) {
const tooltipId = useId();
if (!label) return <>{children}</>;
return (
<span className="relative inline-flex group" tabIndex={0} aria-describedby={tooltipId}>
{children}
<span id={tooltipId} role="tooltip" className={`... group-hover:opacity-100 group-focus-within:opacity-100 ...`}>
{label}
</span>
</span>
);
}d7ce29c to
f296134
Compare
There was a problem hiding this comment.
Findings
- [Major]
Tooltipnow makes every wrapped control add an extra keyboard tab stop because the wrapper is always focusable (tabIndex={0}), even when the child control is enabled and already keyboard-focusable. This degrades keyboard navigation order across existing always-enabled tooltip usage and is directly caused by the new global wrapper behavior. Evidencepackages/ui/src/components/Tooltip.tsx:24.
Suggested fix:import { isValidElement, useId, type ReactNode } from 'react'; export function Tooltip({ label, side = 'bottom', children }: TooltipProps) { const tooltipId = useId(); if (!label) return <>{children}</>; const childDisabled = isValidElement<{ disabled?: boolean }>(children) && Boolean(children.props.disabled); return ( <span className="relative inline-flex group focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded-[var(--radius-sm)]" tabIndex={childDisabled ? 0 : undefined} aria-describedby={childDisabled ? tooltipId : undefined} > {children} <span id={tooltipId} role="tooltip" className={`... group-hover:opacity-100 group-focus-within:opacity-100 ...`}> {label} </span> </span> ); }
Summary
- Review mode: follow-up after new commits
- Found 1 issue in the latest head diff.
docs/VISION.md: Not found in repo/docs.docs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
| <span className="relative inline-flex group"> | ||
| <span | ||
| className="relative inline-flex group focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded-[var(--radius-sm)]" | ||
| tabIndex={0} |
There was a problem hiding this comment.
[Major] tabIndex={0} is now unconditional, so enabled controls wrapped in Tooltip become two tab stops (wrapper + control). This regresses keyboard navigation order. Suggested fix:
const childDisabled =
isValidElement<{ disabled?: boolean }>(children) && Boolean(children.props.disabled);
<span
tabIndex={childDisabled ? 0 : undefined}
aria-describedby={childDisabled ? tooltipId : undefined}
>Implements §9 P1 of the UX master plan. Every disabled interactive button now shows a tooltip on hover explaining why it cannot be clicked. - Sidebar send button: "Type a prompt to start" / "Generation in progress" - InlineCommentComposer apply button: "Type a comment to apply" / "Generation in progress" - PreviewToolbar export button: "Generate a design first" - Settings validate button: "Enter an API key to validate" - Settings save-provider button: "Validate the key first" - Onboarding PasteKey test-connection button: "Enter provider, key and base URL to test" - Onboarding PasteKey continue button: "Validate your API key to continue" - Onboarding ChooseModel back/finish buttons: "Saving in progress" / "Both model fields are required" - Onboarding Welcome Ollama button: "Ollama integration is coming in v0.2" Tooltip strings added to `disabledReason.*` namespace in both en and zh-CN locales. Tooltip component updated to accept `label?: string` and skip rendering when label is undefined, so enabled buttons stay clean. Signed-off-by: hqhq1025 <[email protected]>
All four files that added hardcoded English strings in the previous
tooltip round now use t('disabledReason.*') instead. No new i18n keys
were required — all keys already existed in both en.json and zh-CN.json.
Signed-off-by: hqhq1025 <[email protected]>
Signed-off-by: hqhq1025 <[email protected]>
… + aria-describedby) Signed-off-by: hqhq1025 <[email protected]>
Previously the Tooltip wrapper was unconditionally `tabIndex={0}`, which
added a redundant extra tab stop in front of every enabled control it
wrapped. Now we inspect `children.props.disabled` and only set
`tabIndex={0}` when the wrapped element is actually disabled — enabled
elements stay the sole focus target.
`aria-describedby` remains on the wrapper unconditionally so screen
readers announce the reason whether focus lands on the wrapper or on
the inner control.
Tests:
- "tabindex=0 only when disabled" replaces the previous unconditional check
- "omits tabindex when enabled" guards against regression
- "tab order" verifies the inner button is the first focus target when
enabled and the wrapper takes over only when disabled
Signed-off-by: hqhq1025 <[email protected]>
f296134 to
7b0674b
Compare
There was a problem hiding this comment.
Findings
- No high-confidence issues found in added/modified lines.
Summary
- Review mode: follow-up after new commits
- No blocking/major/minor issues identified in the latest head diff.
docs/VISION.md: Not found in repo/docs.docs/PRINCIPLES.md: Not found in repo/docs.- Residual risk: broad UI behavior across all tooltip callsites cannot be fully validated here because runtime tests were not executable in this environment.
Testing
- Not run (automation):
pnpmis unavailable in this runner (pnpm: command not found).
open-codesign Bot
* feat(ui): add tooltips explaining why every disabled button is disabled Implements §9 P1 of the UX master plan. Every disabled interactive button now shows a tooltip on hover explaining why it cannot be clicked. - Sidebar send button: "Type a prompt to start" / "Generation in progress" - InlineCommentComposer apply button: "Type a comment to apply" / "Generation in progress" - PreviewToolbar export button: "Generate a design first" - Settings validate button: "Enter an API key to validate" - Settings save-provider button: "Validate the key first" - Onboarding PasteKey test-connection button: "Enter provider, key and base URL to test" - Onboarding PasteKey continue button: "Validate your API key to continue" - Onboarding ChooseModel back/finish buttons: "Saving in progress" / "Both model fields are required" - Onboarding Welcome Ollama button: "Ollama integration is coming in v0.2" Tooltip strings added to `disabledReason.*` namespace in both en and zh-CN locales. Tooltip component updated to accept `label?: string` and skip rendering when label is undefined, so enabled buttons stay clean. Signed-off-by: hqhq1025 <[email protected]> * fix(ui): wire disabled tooltip labels through i18n All four files that added hardcoded English strings in the previous tooltip round now use t('disabledReason.*') instead. No new i18n keys were required — all keys already existed in both en.json and zh-CN.json. Signed-off-by: hqhq1025 <[email protected]> * test(ui): cover Tooltip fallback / disabled / hover states Signed-off-by: hqhq1025 <[email protected]> * fix(ui): make disabled tooltip keyboard accessible (focusable wrapper + aria-describedby) Signed-off-by: hqhq1025 <[email protected]> * fix(ui): make Tooltip wrapper focusable only when child disabled Previously the Tooltip wrapper was unconditionally `tabIndex={0}`, which added a redundant extra tab stop in front of every enabled control it wrapped. Now we inspect `children.props.disabled` and only set `tabIndex={0}` when the wrapped element is actually disabled — enabled elements stay the sole focus target. `aria-describedby` remains on the wrapper unconditionally so screen readers announce the reason whether focus lands on the wrapper or on the inner control. Tests: - "tabindex=0 only when disabled" replaces the previous unconditional check - "omits tabindex when enabled" guards against regression - "tab order" verifies the inner button is the first focus target when enabled and the wrapper takes over only when disabled Signed-off-by: hqhq1025 <[email protected]> --------- Signed-off-by: hqhq1025 <[email protected]>
Summary
docs/research/18-ux-master-plan.md: everydisabledbutton now explains why on hoverdisabledReason.*i18n namespace with 12 keys in bothenandzh-CNlocalesTooltipcomponent updated to acceptlabel?: stringand skip rendering whenundefined, so enabled buttons stay cleanCompatibility checklist
labelwidens tostring | undefined(backward compatible)Tooltipprimitive frompackages/uiTooltipTest plan
pnpm -r test)pnpm -r typecheck)pnpm lint)