Skip to content

Commit 18acf50

Browse files
committed
Unify fixed-key hints with the shortcut chip
- Render fixed interaction keys with literal-mode `ShortcutChip`s so they match the rest of the app's shortcut styling: search empty-state tip (`⌘N` / `⌘H` / `⌘Enter`), the run button's `⏎`, the scope popover's `⌥C` / `⌥V`, the recent-items footer `⌘H` and popover `↑↓` / `Enter`, the viewer's binary-warning `⇧Space` / `Enter`, `LoadingIcon`'s cancel `Esc`, `PtpcameradDialog`'s `Ctrl+C`, and the network browser's `⌘R` refresh hint. - Drop the now-dead ad-hoc `<kbd>` / hint-span styles each site carried; the chip is the single source of truth for the look. - Keep, by design, the contexts where a boxed chip reads worse: the mode-chip `.tg-hint` glyphs, the query-dialog footer action-button suffix hints, and the viewer status-bar's dense one-line shortcut list. Rationale recorded in `lib/ui/CLAUDE.md`. - Viewer bundle stays clean: the chip's deep-link helper is lazy-imported, so literal chips ship to the capability-restricted viewer window with zero Tauri window-creation surface at module eval. - Update touched component tests and the `lib/ui` / `lib/query-ui` docs.
1 parent 123e76b commit 18acf50

13 files changed

Lines changed: 84 additions & 87 deletions

File tree

apps/desktop/src/lib/file-explorer/network/NetworkBrowser.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import { handleNavigationShortcut } from '../navigation/keyboard-shortcuts'
3737
import { confirmDialog } from '$lib/utils/confirm-dialog'
3838
import { addToast } from '$lib/ui/toast'
39+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
3940
import { triggerNetworkDiscovery } from './lazy-trigger'
4041
4142
/** Row height for host list (matches Full list) */
@@ -629,7 +630,7 @@
629630
{#if hosts.length > 0}
630631
<button class="network-status-bar" onclick={handleRefreshClick} aria-label="Refresh network hosts">
631632
<span class="status-text">{hosts.length} {hosts.length === 1 ? 'host' : 'hosts'}</span>
632-
<span class="refresh-hint">Press ⌘R or click here to refresh</span>
633+
<span class="refresh-hint">Press <ShortcutChip key="⌘R" size="sm" /> or click here to refresh</span>
633634
</button>
634635
{/if}
635636
</div>
@@ -811,5 +812,8 @@
811812
padding-left: var(--spacing-md);
812813
color: var(--color-text-tertiary);
813814
white-space: nowrap;
815+
display: inline-flex;
816+
align-items: center;
817+
gap: var(--spacing-xxs);
814818
}
815819
</style>

apps/desktop/src/lib/mtp/PtpcameradDialog.svelte

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ModalDialog from '$lib/ui/ModalDialog.svelte'
55
import CommandBox from '$lib/ui/CommandBox.svelte'
66
import Button from '$lib/ui/Button.svelte'
7+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
78
89
interface Props {
910
/** The process name that's blocking (like "pid 45145, ptpcamerad"). */
@@ -60,8 +61,8 @@
6061
</div>
6162

6263
<p class="help-text">
63-
This command continuously stops ptpcamerad while running. Press <kbd>Ctrl+C</kbd> in Terminal to stop it when
64-
done.
64+
This command continuously stops ptpcamerad while running. Press <ShortcutChip key="Ctrl+C" /> in Terminal to
65+
stop it when done.
6566
</p>
6667

6768
<div class="actions">
@@ -114,15 +115,6 @@
114115
line-height: 1.5;
115116
}
116117
117-
.help-text kbd {
118-
background: var(--color-bg-tertiary);
119-
padding: var(--spacing-xxs) var(--spacing-xs);
120-
border-radius: var(--radius-sm);
121-
font-family: var(--font-system);
122-
font-size: var(--font-size-sm);
123-
border: 1px solid var(--color-border-strong);
124-
}
125-
126118
.actions {
127119
display: flex;
128120
gap: var(--spacing-md);

apps/desktop/src/lib/query-ui/CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ Small contracts that apply to every consumer of the query UI:
191191
- Each chip's tooltip leads with the full text so a CSS-ellipsis-truncated chip stays readable on hover.
192192
- Path column font is `--font-size-sm` (matching the filename column) with `--spacing-xxs` row vertical padding so the
193193
row height stays compact.
194+
- **Fixed interaction keys render as literal `ShortcutChip`s** (`size="sm"` in dense slots): the run button's ``, the
195+
empty-state tip (`⌘N` / `⌘H` / `⌘Enter`, in `EmptyState.svelte`), the scope popover's `⌥C` / `⌥V`, and the recent-items
196+
footer's `⌘H` and popover's `↑↓` / `Enter`. These are dialog-internal keys with no registry command, so the chip only
197+
unifies their look — never clickable, never dynamic. The mode-chip `.tg-hint` glyphs (`⌥A` / `⌥F` / `⌥R`) and the
198+
footer action-button hints (`Go to file ⏎`, `Show all in main window ⏎`) deliberately stay un-boxed; see
199+
`lib/ui/CLAUDE.md` § ShortcutChip for the rationale.
194200

195201
Chip-side behaviors live in [`filter-chips/CLAUDE.md`](filter-chips/CLAUDE.md); search-specific ones in
196202
`lib/search/CLAUDE.md`.

apps/desktop/src/lib/query-ui/EmptyState.svelte

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
import { formatNumber } from '$lib/file-explorer/selection/selection-info-utils'
1717
import { pluralize } from '$lib/utils/pluralize'
18+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
1819
import type { SearchMode } from './query-filter-state.svelte'
1920
2021
interface ExampleChip {
@@ -89,9 +90,9 @@
8990
<p class="index-status">Index ready · {formattedCount} {pluralize(indexEntryCount, 'entry', 'entries')}</p>
9091
{/if}
9192
<p class="tip">
92-
Tip: <kbd>⌘N</kbd> starts fresh, <kbd>⌘H</kbd> shows recent searches{#if aiEnabled}, <kbd
93-
>⌘Enter</kbd
94-
> runs an AI search{/if}.
93+
Tip: <ShortcutChip key="⌘N" /> starts fresh, <ShortcutChip key="⌘H" /> shows recent searches{#if aiEnabled}, <ShortcutChip
94+
key="⌘Enter"
95+
/> runs an AI search{/if}.
9596
</p>
9697
</div>
9798

@@ -173,15 +174,10 @@
173174
color: var(--color-text-tertiary);
174175
font-size: var(--font-size-xs);
175176
margin: 0;
176-
}
177-
178-
kbd {
179-
font-family: var(--font-mono);
180-
font-size: var(--font-size-xs);
181-
background: var(--color-bg-tertiary);
182-
border: 1px solid var(--color-border-subtle);
183-
border-radius: var(--radius-xs);
184-
padding: 0 var(--spacing-xxs);
185-
color: var(--color-text-primary);
177+
display: inline-flex;
178+
flex-wrap: wrap;
179+
align-items: center;
180+
justify-content: center;
181+
gap: var(--spacing-xxs);
186182
}
187183
</style>

apps/desktop/src/lib/query-ui/QueryBar.svelte

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* - ⌘1/⌘2/⌘3 switch modes (numbering changes when AI is off).
2121
*/
2222
import { tooltip } from '$lib/tooltip/tooltip'
23+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
2324
import type { SearchMode } from './query-filter-state.svelte'
2425
2526
interface Props {
@@ -122,7 +123,7 @@
122123
aria-label={runTitle}
123124
>
124125
<span class="run-label">Search</span>
125-
{#if showEnterHint}<span class="run-enter-hint" aria-hidden="true">⏎</span>{/if}
126+
{#if showEnterHint}<ShortcutChip key="⏎" size="sm" />{/if}
126127
</button>
127128
</div>
128129

@@ -195,13 +196,6 @@
195196
line-height: 1;
196197
}
197198
198-
.run-enter-hint {
199-
font-family: var(--font-mono);
200-
font-size: var(--font-size-xs);
201-
color: var(--color-text-tertiary);
202-
opacity: 0.8;
203-
}
204-
205199
.run-button:hover:not(:disabled) {
206200
background: var(--color-bg-tertiary);
207201
color: var(--color-text-primary);

apps/desktop/src/lib/query-ui/QueryBar.svelte.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ describe('SearchBar', () => {
118118
// No leading icon. The corner-down-left lucide icon used to live here.
119119
const svgs = button?.querySelectorAll('svg') ?? []
120120
expect(svgs.length).toBe(0)
121-
// Exactly one "⏎" hint inside the button.
122-
const enterHints = button?.querySelectorAll('.run-enter-hint') ?? []
121+
// Exactly one "⏎" hint chip inside the button.
122+
const enterHints = button?.querySelectorAll('.shortcut-chip') ?? []
123123
expect(enterHints.length).toBe(1)
124+
expect(enterHints[0]?.textContent).toBe('⏎')
124125
// The visible label reads "Search ⏎" (a single space between "Search" and "⏎").
125126
const text = button?.textContent.replace(/\s+/g, ' ').trim()
126127
expect(text).toBe('Search ⏎')

apps/desktop/src/lib/query-ui/filter-chips/ScopeFilterPopover.svelte

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* stays in the parent so the dialog-level keymap lives next to the popovers it targets.
1010
*/
1111
import FilterChipPopover from './FilterChipPopover.svelte'
12+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
1213
import { tooltip } from '$lib/tooltip/tooltip'
1314
import './filter-popover.css'
1415
@@ -126,7 +127,7 @@
126127
}}
127128
>
128129
Use current folder
129-
<kbd class="footer-kbd">⌥C</kbd>
130+
<ShortcutChip key="⌥C" size="sm" />
130131
</button>
131132
<button
132133
type="button"
@@ -137,7 +138,7 @@
137138
}}
138139
>
139140
All folders
140-
<kbd class="footer-kbd">⌥V</kbd>
141+
<ShortcutChip key="⌥V" size="sm" />
141142
</button>
142143
</div>
143144
</div>
@@ -234,16 +235,4 @@
234235
opacity: 0.5;
235236
cursor: not-allowed;
236237
}
237-
238-
.footer-kbd {
239-
font-family: var(--font-mono);
240-
font-size: var(--font-size-xs);
241-
font-weight: 500;
242-
color: var(--color-accent-text);
243-
background: var(--color-accent-subtle);
244-
/* stylelint-disable-next-line declaration-property-value-disallowed-list */
245-
padding: 1px 4px;
246-
border-radius: var(--radius-sm);
247-
line-height: 1;
248-
}
249238
</style>

apps/desktop/src/lib/query-ui/recent-items/RecentItemsFooter.svelte

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
import { onDestroy, tick } from 'svelte'
2020
import { tooltip } from '$lib/tooltip/tooltip'
21+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
2122
import { computeRecentChipsLayout } from '$lib/query-ui/recent-chips-layout'
2223
import { modeBadge } from './recent-items-utils'
2324
import type { RecentItemAdapter, RecentItemKey } from './recent-items-types'
@@ -210,7 +211,7 @@
210211
use:tooltip={{ text: trailingTooltipText, shortcut: trailingShortcut }}
211212
aria-label={ariaAllButtonLabel}
212213
>
213-
{trailingLabel}<span class="shortcut-hint" aria-hidden="true">{trailingShortcut}</span>
214+
{trailingLabel}<ShortcutChip key={trailingShortcut} size="sm" />
214215
</button>
215216
</div>
216217
{/if}
@@ -313,15 +314,4 @@
313314
font-style: italic;
314315
color: var(--color-text-tertiary);
315316
}
316-
317-
/* Inline ⌘H hint after the "All searches…" label. Tertiary color so it
318-
reads as discoverability cue, not action label. */
319-
.shortcut-hint {
320-
margin-left: var(--spacing-xs);
321-
font-family: var(--font-mono);
322-
font-size: var(--font-size-xs);
323-
color: var(--color-text-tertiary);
324-
opacity: 0.8;
325-
font-style: normal;
326-
}
327317
</style>

apps/desktop/src/lib/query-ui/recent-items/RecentItemsPopover.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
import uFuzzy from '@leeoniya/ufuzzy'
2121
import FilterChipPopover from '../filter-chips/FilterChipPopover.svelte'
22+
import ShortcutChip from '$lib/ui/ShortcutChip.svelte'
2223
import { modeBadge } from './recent-items-utils'
2324
import type { RecentItemAdapter, RecentItemKey, RecentItemView } from './recent-items-types'
2425
@@ -195,7 +196,10 @@
195196
{/each}
196197
{/if}
197198
</div>
198-
<div class="hint">↑↓ to move · Enter to run · right-click to remove</div>
199+
<div class="hint">
200+
<ShortcutChip key="↑↓" size="sm" /> to move · <ShortcutChip key="Enter" size="sm" /> to run · right-click to
201+
remove
202+
</div>
199203
</div>
200204
</FilterChipPopover>
201205

@@ -278,6 +282,10 @@
278282
}
279283
280284
.hint {
285+
display: flex;
286+
flex-wrap: wrap;
287+
align-items: center;
288+
gap: var(--spacing-xxs);
281289
color: var(--color-text-tertiary);
282290
font-size: var(--font-size-xs);
283291
padding-top: var(--spacing-xxs);

apps/desktop/src/lib/ui/CLAUDE.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ Progressive status text driven by props (mutually exclusive, evaluated top-down)
184184
3. `openingFolder` true → "Opening folder..."
185185
4. Default → "Loading..."
186186

187-
`showCancelHint` adds "Press ESC to cancel and go back" below the spinner. The container uses a 400ms `fadeIn` animation
187+
`showCancelHint` adds "Press [Esc] to cancel and go back" (the key rendered as a literal `ShortcutChip`) below the
188+
spinner. The container uses a 400ms `fadeIn` animation
188189
where the first 50% is invisible (effectively 200ms before fade begins), avoiding flash for fast loads.
189190

190191
## ProgressBar
@@ -375,6 +376,31 @@ command palette (up to three chips per row) is the first consumer.
375376
The `shortcut-<commandId>` anchor-id convention (shared with the Settings section the deep link targets) lives as the
376377
exported `shortcutAnchorId(commandId)` in `lib/settings/settings-window.ts` so it can't drift.
377378

379+
**Where literal chips render the fixed interaction keys (Class B).** Beyond the live `commandId` sites, literal-mode
380+
chips give the uniform key look to fixed (non-customizable) interaction keys: the search dialog's empty-state tip
381+
(`⌘N` / `⌘H` / `⌘Enter`), the run button's ``, the scope popover's `⌥C` / `⌥V`, the recent-items footer's `⌘H` and
382+
popover's `↑↓` / `Enter`, the viewer's binary-warning `⇧Space` / `Enter`, `LoadingIcon`'s `Esc` cancel hint, the
383+
`PtpcameradDialog` `Ctrl+C`, and the network browser's `⌘R` refresh hint. These keys are static by nature (no registry
384+
command, never clickable); the chip only unifies their appearance.
385+
386+
**Class B sites kept un-migrated (deliberate exceptions):**
387+
388+
- **`ModeChips` / `ToggleGroup` `.tg-hint` glyphs** (`⌥A` / `⌥F` / `⌥R`): these are whisper-quiet tertiary mono text
389+
baked into a segmented-control cell. A boxed chip reads heavier than the surrounding cell label and fights the
390+
control's calm rhythm, so the hint style stays. (Pre-decided in the plan.)
391+
- **The `QueryDialog` footer action-button hints** (`.shortcut-hint` / `.shortcut-on-primary`, e.g. `Go to file ⏎`,
392+
`Show all in main window ⏎`): the key reads as a suffix fused into the button's own label ("Go to file" + "⏎" is one
393+
phrase). Boxing it fragments the label from the key and the neutral pill clashes on the accent primary button. Kept as
394+
integrated microcopy, same call as the F-key bar in M4.
395+
- **`ViewerStatusBar`'s shortcut line** (`W wrap · F tail · ⌘A select all · …`): a dense single tertiary line of six
396+
key+description pairs. Boxing each key would make the calm status bar loud and risk overflow; the run-on prose form is
397+
the right treatment here.
398+
- **`KeyboardShortcutsSection`'s "Press ESC to clear"**: lives inside the Settings shortcuts editor, which is out of
399+
scope for chip migration (it IS the editor).
400+
401+
The litmus test for a future Class B site: migrate when the chip reads same-or-better than the current treatment; keep
402+
the current treatment (and note it here) when the boxed pill genuinely reads worse in a dense or label-fused context.
403+
378404
## SectionCard
379405

380406
The canonical "grouped card" primitive that mirrors macOS System Settings on Tahoe: an optional label sitting on its own

0 commit comments

Comments
 (0)