Browse Source

fix(web): preserve polish accessibility contracts

imccyu 1 month ago
parent
commit
3be257ba1d

+ 2 - 2
apps/web/tests/snapshots/sidebar-scrollbar/geometry.expected.md

@@ -24,8 +24,8 @@
 - scrollbar-width: auto
 - scrollbar-color: auto
 - ::-webkit-scrollbar-thumb:hover declarations: var(--dsh-scrollbar-thumb-hover)
-- --dsh-scrollbar-thumb: rgb(60, 60, 61)
-- --dsh-scrollbar-thumb-hover: rgb(84, 85, 87)
+- --dsh-scrollbar-thumb: rgb(84, 85, 87)
+- --dsh-scrollbar-thumb-hover: rgb(101, 103, 107)
 - list overflows: true
 - reserved band: 8px
 - relative time covered by the bar: 0px

+ 1 - 1
packages/client/ui-conversation/src/client/chat/StatsLine.tsx

@@ -152,7 +152,7 @@ export const StatsLine = memo(function StatsLine({ useSession, useProjection }:
     <div className={css.root}>
       {groups.map((group, i) => (
         <Fragment key={group}>
-          {i > 0 && <span className={css.sep} aria-hidden>|</span>}
+          {i > 0 && <><span className={css.sep} aria-hidden>|</span>{' '}</>}
           <span>{group}</span>
         </Fragment>
       ))}

+ 3 - 1
packages/client/ui-conversation/src/client/skeleton/PermissionSelect.tsx

@@ -148,7 +148,9 @@ export function PermissionSelect({ value, locked, command, t }: PermissionSelect
             )}
             <span className={css.triggerLabel}>{current === undefined ? displayName(currentValue) : optionLabel(current)}</span>
             {/* Same glyph + open rotation as the sibling ModelSelect trigger. */}
-            <IconChevronDownOutline14 className={clsx(css.chevron, open && css.chevronOpen)} />
+            <span className={clsx(css.chevron, open && css.chevronOpen)} aria-hidden>
+              <IconChevronDownOutline14 />
+            </span>
           </button>
         }
       />

+ 4 - 4
packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx

@@ -133,7 +133,7 @@ describe('StatsLine', () => {
     const view = render(<StatsLine {...props(source)} />)
     // No timing on the fixture: the duration group drops out whole. Tokens come
     // from the projection, so paging the window cannot change them.
-    expect(view.container.textContent).toBe('1 turns · 1 steps|Cache hit 90%|Input 100 tok · Output 5 tok')
+    expect(view.container.textContent).toBe('1 turns · 1 steps| Cache hit 90%| Input 100 tok · Output 5 tok')
     const empty = makeSource()
     const emptyView = render(<StatsLine {...props(empty.source, {
       tokenUsage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },
@@ -149,7 +149,7 @@ describe('StatsLine', () => {
       contextPressure: { pressureTokens: 32_000, contextWindow: 128_000 },
     })} />)
     expect(view.container.textContent)
-      .toBe('Context 25% of 128K|Cache hit 90%|Input 100 tok · Output 5 tok')
+      .toBe('Context 25% of 128K| Cache hit 90%| Input 100 tok · Output 5 tok')
   })
 
   it('renders context occupancy only when the projection knows a capacity', () => {
@@ -196,7 +196,7 @@ describe('StatsLine', () => {
     const view = render(<StatsLine {...props(source, {
       tokenUsage: { uncachedInputTokens: 0, outputTokens: 7, cacheReadTokens: 0, cacheWriteTokens: 0 },
     })} />)
-    expect(view.container.textContent).toBe('1 turns · 1 steps|Input 0 tok · Output 7 tok')
+    expect(view.container.textContent).toBe('1 turns · 1 steps| Input 0 tok · Output 7 tok')
   })
 
   it('includes cache writes in billed input and the cache-hit denominator', () => {
@@ -210,7 +210,7 @@ describe('StatsLine', () => {
       },
     })} />)
     expect(view.container.textContent)
-      .toBe('1 turns · 1 steps|Cache hit 45%|Input 200 tok · Output 7 tok')
+      .toBe('1 turns · 1 steps| Cache hit 45%| Input 200 tok · Output 7 tok')
   })
 
   it('renders ZERO times during streaming chunk frames (RFC hard acceptance)', () => {

+ 2 - 0
packages/client/ui-conversation/tests/input-bar.spec.tsx

@@ -595,6 +595,8 @@ describe('command launcher chrome and control seats', () => {
     const trigger = view.getByLabelText(/^访问模式/) as HTMLButtonElement
     // Title-case display is presentation only; the menu ids stay machine names.
     expect(trigger.textContent).toBe('Read Only')
+    expect([...trigger.querySelectorAll('svg')]
+      .every(icon => icon.closest('[aria-hidden="true"]') !== null)).toBe(true)
     fireEvent.click(trigger)
     const items = view.getAllByRole('menuitem')
     expect(items.map(o => o.textContent)).toEqual(['Read Only', 'Workspace Write', 'Full access'])

+ 2 - 2
packages/client/ui-sidebar/src/client/SidebarRoot.module.css

@@ -23,8 +23,8 @@
 }
 
 .root:hover {
-  --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l1);
-  --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l1);
+  --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
+  --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
 }
 
 /* Rail geometry (figma rail spec): 36x36 control boxes centered in the 56px