feat: allow clicking keyboard hints throughout #266

Merged
talksik merged 3 commits from worktree-harmonic-purring-fox into main 2026-06-11 17:48:59 +00:00
talksik commented 2026-06-11 17:25:42 +00:00 (Migrated from github.com)

Closes #186

Summary by CodeRabbit

  • New Features

    • Added a unified keyboard-hint control and styled key chips for interactive shortcuts across the app.
  • Style

    • Replaced scattered inline key/kbd hints with the unified control, standardizing appearance and behavior in overlays, editors, playback, and navigation.
    • Improved hint wording, titles/ARIA, and click/tap affordances for keyboard shortcuts.
Closes #186 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a unified keyboard-hint control and styled key chips for interactive shortcuts across the app. * **Style** * Replaced scattered inline key/kbd hints with the unified control, standardizing appearance and behavior in overlays, editors, playback, and navigation. * Improved hint wording, titles/ARIA, and click/tap affordances for keyboard shortcuts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-11 17:25:59 +00:00 (Migrated from github.com)

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0523d50-f395-4995-bba9-b64c341a2f72

📥 Commits

Reviewing files that changed from the base of the PR and between dd850d28d1 and 1cf144bce3.

📒 Files selected for processing (2)
  • js/desktop/src/features/particles/stream-view.tsx
  • js/desktop/src/hooks/use-stream-navigation-keys.ts

📝 Walkthrough

Walkthrough

Adds Kbd primitives and a reusable KeyHint component, then replaces inline hint markup across overlays, compose flows, stream view, and other UI with KeyHint instances that optionally handle clicks and preserve focus.

Changes

KeyHint Foundation and Keyboard Shortcut Consolidation

Layer / File(s) Summary
KeyHint component foundation
js/desktop/src/components/ui/kbd.tsx, js/desktop/src/components/key-hint.tsx
New Kbd and KbdGroup primitives and KeyHint component render key chips, support prefix/separator/children, and render as a non-interactive span or clickable button with focus-preserving onMouseDown handling.
Overlay keyboard shortcuts
js/desktop/src/components/confirm-destructive-overlay.tsx, js/desktop/src/components/keybindings-overlay.tsx, js/desktop/src/features/particles/rename-stream-overlay.tsx, js/desktop/src/features/particles/stream-members-overlay.tsx
Overlay components replace inline <kbd> close/navigation hints with KeyHint elements wired to onClose or equivalent handlers and updated title/aria text.
Compose feature keyboard shortcuts
js/desktop/src/features/compose/text-editor.tsx, js/desktop/src/features/compose/recording-overlay.tsx, js/desktop/src/features/compose/compose-overlay.tsx, js/desktop/src/features/compose/configure-stream-step.tsx
Compose-related UIs now use KeyHint for Esc, Enter, Meta+Enter, Meta+M, and other hints, preserving conditional rendering and existing click/keyboard handlers.
Additional component keyboard shortcuts
js/desktop/src/components/video-audio-toggle.tsx, js/desktop/src/features/attachments/attachment-lightbox.tsx, js/desktop/src/components/keybindings-overlay.tsx, js/desktop/src/features/network-root.tsx
Other components migrated from inline <kbd>/button markup to KeyHint, delegating click behavior to existing handlers and adding title/aria attributes where applicable.
Stream view controls and huddle integration
js/desktop/src/features/particles/stream-view.tsx, js/desktop/src/hooks/use-stream-navigation-keys.ts
StreamViewControls refactored to use KeyHint for in-stream actions and receives onOpenHuddle and onExit callbacks; useStreamNavigationKeys now accepts onExit and calls it on Escape.

🎯 3 (Moderate) | ⏱️ ~22 minutes

"🐰
Keys once scattered, now they unite,
One component to make hints bright,
Click or type, both work just right,
Keyboard flows polished in sight,
Hop on — shortcuts feel light!"

🚥 Pre-merge checks | 4 | 1

Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title 'feat: allow clicking keyboard hints throughout' clearly and concisely describes the main change—enabling keyboard hint clicks across the application.
Linked Issues check Passed The PR successfully addresses issue #186 by implementing clickable keyboard shortcut hints via the new KeyHint component, allowing mouse-click alternatives throughout the UI.
Out of Scope Changes check Passed All changes are directly aligned with the PR objectives. New components (KeyHint, Kbd, KbdGroup) support the feature, and refactored files consistently replace hardcoded keyboard hints with the reusable KeyHint component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-harmonic-purring-fox

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/flowy-live/llink/pull/266?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> No actionable comments were generated in the recent review. 🎉 <details> <summary>ℹ️ Recent review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `e0523d50-f395-4995-bba9-b64c341a2f72` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between dd850d28d13405a8065caccfef5fb52ed148ad18 and 1cf144bce3c8a2d6bf8f414148c22b9ec2781abf. </details> <details> <summary>📒 Files selected for processing (2)</summary> * `js/desktop/src/features/particles/stream-view.tsx` * `js/desktop/src/hooks/use-stream-navigation-keys.ts` </details> </details> --- <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough Adds Kbd primitives and a reusable KeyHint component, then replaces inline <kbd> hint markup across overlays, compose flows, stream view, and other UI with KeyHint instances that optionally handle clicks and preserve focus. ## Changes **KeyHint Foundation and Keyboard Shortcut Consolidation** | Layer / File(s)|Summary| |---|---| |**KeyHint component foundation** <br> `js/desktop/src/components/ui/kbd.tsx`, `js/desktop/src/components/key-hint.tsx`|New `Kbd` and `KbdGroup` primitives and `KeyHint` component render key chips, support `prefix`/`separator`/`children`, and render as a non-interactive `span` or clickable `button` with focus-preserving `onMouseDown` handling.| |**Overlay keyboard shortcuts** <br> `js/desktop/src/components/confirm-destructive-overlay.tsx`, `js/desktop/src/components/keybindings-overlay.tsx`, `js/desktop/src/features/particles/rename-stream-overlay.tsx`, `js/desktop/src/features/particles/stream-members-overlay.tsx`|Overlay components replace inline `<kbd>` close/navigation hints with `KeyHint` elements wired to `onClose` or equivalent handlers and updated title/aria text.| |**Compose feature keyboard shortcuts** <br> `js/desktop/src/features/compose/text-editor.tsx`, `js/desktop/src/features/compose/recording-overlay.tsx`, `js/desktop/src/features/compose/compose-overlay.tsx`, `js/desktop/src/features/compose/configure-stream-step.tsx`|Compose-related UIs now use `KeyHint` for Esc, Enter, Meta+Enter, Meta+M, and other hints, preserving conditional rendering and existing click/keyboard handlers.| |**Additional component keyboard shortcuts** <br> `js/desktop/src/components/video-audio-toggle.tsx`, `js/desktop/src/features/attachments/attachment-lightbox.tsx`, `js/desktop/src/components/keybindings-overlay.tsx`, `js/desktop/src/features/network-root.tsx`|Other components migrated from inline `<kbd>`/button markup to `KeyHint`, delegating click behavior to existing handlers and adding title/aria attributes where applicable.| |**Stream view controls and huddle integration** <br> `js/desktop/src/features/particles/stream-view.tsx`, `js/desktop/src/hooks/use-stream-navigation-keys.ts`|`StreamViewControls` refactored to use `KeyHint` for in-stream actions and receives `onOpenHuddle` and `onExit` callbacks; `useStreamNavigationKeys` now accepts `onExit` and calls it on Escape.| 🎯 3 (Moderate) | ⏱️ ~22 minutes > "🐰 > Keys once scattered, now they unite, > One component to make hints bright, > Click or type, both work just right, > Keyboard flows polished in sight, > Hop on — shortcuts feel light!" </details> <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> <details> <summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary> ### ❌ Failed checks (1 warning) | Check name | Status | Explanation | Resolution | | :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | <details> <summary>✅ Passed checks (4 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title check | ✅ Passed | The title 'feat: allow clicking keyboard hints throughout' clearly and concisely describes the main change—enabling keyboard hint clicks across the application. | | Linked Issues check | ✅ Passed | The PR successfully addresses issue `#186` by implementing clickable keyboard shortcut hints via the new KeyHint component, allowing mouse-click alternatives throughout the UI. | | Out of Scope Changes check | ✅ Passed | All changes are directly aligned with the PR objectives. New components (KeyHint, Kbd, KbdGroup) support the feature, and refactored files consistently replace hardcoded keyboard hints with the reusable KeyHint component. | </details> <sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub> </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>📝 Generate docstrings</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR - [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch </details> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `worktree-harmonic-purring-fox` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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= --> <!-- internal state end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-06-11 17:31:13 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@js/desktop/src/components/key-hint.tsx`:
- Around line 45-49: The Fragment in KeyHint maps keyList with <Fragment
key={k}> which can collide when labels repeat; change the key to a composite
unique identifier (for example using the index) such as `${k}-${i}` to avoid
React reconciliation issues — locate the map in the KeyHint component
(keyList.map((k, i) => ...)) and replace the Fragment key to use both the label
and index (or just the index if appropriate) so each rendered element has a
stable, unique key.

In `@js/desktop/src/components/ui/kbd.tsx`:
- Around line 16-23: KbdGroup is typed as React.ComponentProps<'div'> but
returns a <kbd>, causing a prop-type mismatch; fix by making the component's
props match the rendered element: either change the render to a <div> in the
KbdGroup function or update the props typing to React.ComponentProps<'kbd'>
(adjust any consumers if needed), ensuring the component signature (KbdGroup)
and spread {...props} align with the element type.

In `@js/desktop/src/features/particles/stream-view.tsx`:
- Around line 610-616: The Escape action currently uses history back
(navigate(-1)) which can leave users off the network root; update the Escape
handlers to navigate to the network root instead: replace navigate(-1) in the
KeyHint onClick (KeyHint in stream-view.tsx) and the Escape case in
useStreamNavigationKeys to call navigate(`/${networkId}`) (use the same
networkId used by handleExitNavigate) so the keybinding, title, and exit
countdown all consistently go to the network root; alternatively, if you prefer
history semantics, change the KeyHint title/text from “Back to network” to
“Back”/“History back” instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 390544c3-d236-4d15-982a-436bf62b136b

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd7e611f3 and 023e332e01.

📒 Files selected for processing (14)
  • js/desktop/src/components/confirm-destructive-overlay.tsx
  • js/desktop/src/components/key-hint.tsx
  • js/desktop/src/components/keybindings-overlay.tsx
  • js/desktop/src/components/ui/kbd.tsx
  • js/desktop/src/components/video-audio-toggle.tsx
  • js/desktop/src/features/attachments/attachment-lightbox.tsx
  • js/desktop/src/features/compose/compose-overlay.tsx
  • js/desktop/src/features/compose/configure-stream-step.tsx
  • js/desktop/src/features/compose/recording-overlay.tsx
  • js/desktop/src/features/compose/text-editor.tsx
  • js/desktop/src/features/network-root.tsx
  • js/desktop/src/features/particles/rename-stream-overlay.tsx
  • js/desktop/src/features/particles/stream-members-overlay.tsx
  • js/desktop/src/features/particles/stream-view.tsx
**Actionable comments posted: 3** <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Inline comments: In `@js/desktop/src/components/key-hint.tsx`: - Around line 45-49: The Fragment in KeyHint maps keyList with <Fragment key={k}> which can collide when labels repeat; change the key to a composite unique identifier (for example using the index) such as `${k}-${i}` to avoid React reconciliation issues — locate the map in the KeyHint component (keyList.map((k, i) => ...)) and replace the Fragment key to use both the label and index (or just the index if appropriate) so each rendered element has a stable, unique key. In `@js/desktop/src/components/ui/kbd.tsx`: - Around line 16-23: KbdGroup is typed as React.ComponentProps<'div'> but returns a <kbd>, causing a prop-type mismatch; fix by making the component's props match the rendered element: either change the render to a <div> in the KbdGroup function or update the props typing to React.ComponentProps<'kbd'> (adjust any consumers if needed), ensuring the component signature (KbdGroup) and spread {...props} align with the element type. In `@js/desktop/src/features/particles/stream-view.tsx`: - Around line 610-616: The Escape action currently uses history back (navigate(-1)) which can leave users off the network root; update the Escape handlers to navigate to the network root instead: replace navigate(-1) in the KeyHint onClick (KeyHint in stream-view.tsx) and the Escape case in useStreamNavigationKeys to call navigate(`/${networkId}`) (use the same networkId used by handleExitNavigate) so the keybinding, title, and exit countdown all consistently go to the network root; alternatively, if you prefer history semantics, change the KeyHint title/text from “Back to network” to “Back”/“History back” instead. ``` </details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `390544c3-d236-4d15-982a-436bf62b136b` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 9fd7e611f31d09957648ccb5117fc12bbdc6c0bf and 023e332e011714c127bba86ea85b1fada81d43fc. </details> <details> <summary>📒 Files selected for processing (14)</summary> * `js/desktop/src/components/confirm-destructive-overlay.tsx` * `js/desktop/src/components/key-hint.tsx` * `js/desktop/src/components/keybindings-overlay.tsx` * `js/desktop/src/components/ui/kbd.tsx` * `js/desktop/src/components/video-audio-toggle.tsx` * `js/desktop/src/features/attachments/attachment-lightbox.tsx` * `js/desktop/src/features/compose/compose-overlay.tsx` * `js/desktop/src/features/compose/configure-stream-step.tsx` * `js/desktop/src/features/compose/recording-overlay.tsx` * `js/desktop/src/features/compose/text-editor.tsx` * `js/desktop/src/features/network-root.tsx` * `js/desktop/src/features/particles/rename-stream-overlay.tsx` * `js/desktop/src/features/particles/stream-members-overlay.tsx` * `js/desktop/src/features/particles/stream-view.tsx` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@@ -0,0 +46,4 @@
<Fragment key={`${k}-${i}`}>
{i > 0 && (separator != null ? <> {separator} </> : ' ')}
<Kbd className={chipClass}>{k}</Kbd>
</Fragment>
coderabbitai[bot] (Migrated from github.com) commented 2026-06-11 17:31:11 +00:00

⚠️ Potential issue | 🟡 Minor

Prevent React key collisions for repeated labels in KeyHint.
<Fragment key={k}> uses the label as the React key; if keyList contains duplicate labels (e.g. "Shift" twice), reconciliation can be incorrect. Use a composite key with the index.

Proposed fix
-      {keyList.map((k, i) => (
-        <Fragment key={k}>
+      {keyList.map((k, i) => (
+        <Fragment key={`${k}-${i}`}>
           {i > 0 && (separator != null ? <> {separator} </> : ' ')}
           <Kbd className={chipClass}>{k}</Kbd>
         </Fragment>
       ))}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      {keyList.map((k, i) => (
        <Fragment key={`${k}-${i}`}>
          {i > 0 && (separator != null ? <> {separator} </> : ' ')}
          <Kbd className={chipClass}>{k}</Kbd>
        </Fragment>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/desktop/src/components/key-hint.tsx` around lines 45 - 49, The Fragment in
KeyHint maps keyList with <Fragment key={k}> which can collide when labels
repeat; change the key to a composite unique identifier (for example using the
index) such as `${k}-${i}` to avoid React reconciliation issues — locate the map
in the KeyHint component (keyList.map((k, i) => ...)) and replace the Fragment
key to use both the label and index (or just the index if appropriate) so each
rendered element has a stable, unique key.

Addressed in commit dd850d2

_⚠️ Potential issue_ | _🟡 Minor_ **Prevent React key collisions for repeated labels in `KeyHint`.** `<Fragment key={k}>` uses the label as the React key; if `keyList` contains duplicate labels (e.g. `"Shift"` twice), reconciliation can be incorrect. Use a composite key with the index. <details> <summary>Proposed fix</summary> ```diff - {keyList.map((k, i) => ( - <Fragment key={k}> + {keyList.map((k, i) => ( + <Fragment key={`${k}-${i}`}> {i > 0 && (separator != null ? <> {separator} </> : ' ')} <Kbd className={chipClass}>{k}</Kbd> </Fragment> ))} ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion {keyList.map((k, i) => ( <Fragment key={`${k}-${i}`}> {i > 0 && (separator != null ? <> {separator} </> : ' ')} <Kbd className={chipClass}>{k}</Kbd> </Fragment> ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/components/key-hint.tsx` around lines 45 - 49, The Fragment in KeyHint maps keyList with <Fragment key={k}> which can collide when labels repeat; change the key to a composite unique identifier (for example using the index) such as `${k}-${i}` to avoid React reconciliation issues — locate the map in the KeyHint component (keyList.map((k, i) => ...)) and replace the Fragment key to use both the label and index (or just the index if appropriate) so each rendered element has a stable, unique key. ``` </details> <!-- fingerprinting:phantom:poseidon:hawk --> <!-- cr-comment:v1:f704eb304db8d85483dc9879 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit dd850d2
@@ -0,0 +20,4 @@
className={cn('inline-flex items-center gap-1', className)}
{...props}
/>
);
coderabbitai[bot] (Migrated from github.com) commented 2026-06-11 17:31:12 +00:00

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="js/desktop/src/components/ui/kbd.tsx"
echo "== File =="
ls -l "$FILE"

echo "== Lines 1-120 =="
nl -ba "$FILE" | sed -n '1,120p'

Repository: flowy-live/llink

Length of output: 235


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find usages of KbdGroup
rg -n --hidden --no-ignore-vcs "\bKbdGroup\b" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if there's another KbdGroup defined elsewhere in repo
rg -n --hidden --no-ignore-vcs "function KbdGroup|const KbdGroup|export .*KbdGroup" js || true

Repository: flowy-live/llink

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="js/desktop/src/components/ui/kbd.tsx"
echo "== Lines 1-60 =="
cat -n "$FILE" | sed -n '1,60p'

Repository: flowy-live/llink

Length of output: 1101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Usage of the component in TSX/TS
rg -n --hidden --no-ignore-vcs "<KbdGroup\\b|KbdGroup\\s*\\(" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for the slot attribute usage to understand expected semantics
rg -n --hidden --no-ignore-vcs "data-slot=['\"]kbd-group['\"]" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 128


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all usages of KbdGroup (JS/TSX)
rg -n --hidden --no-ignore-vcs "\b<KbdGroup\b|\bKbdGroup\s*\(" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all occurrences of the data-slot value to infer expected markup
rg -n --hidden --no-ignore-vcs "data-slot=['\"]kbd-group['\"]|kbd-group" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 128


Fix KbdGroup element/prop-type mismatch

KbdGroup is typed as React.ComponentProps<'div'> but renders a <kbd>, creating an API contract mismatch for consumers. Render a <div> (or change the props typing to 'kbd' if <kbd> is intentional).

Proposed fix
 function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
   return (
-    <kbd
+    <div
       data-slot="kbd-group"
       className={cn('inline-flex items-center gap-1', className)}
       {...props}
-    />
+    />
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot="kbd-group"
      className={cn('inline-flex items-center gap-1', className)}
      {...props}
    />
  );
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/desktop/src/components/ui/kbd.tsx` around lines 16 - 23, KbdGroup is typed
as React.ComponentProps<'div'> but returns a <kbd>, causing a prop-type
mismatch; fix by making the component's props match the rendered element: either
change the render to a <div> in the KbdGroup function or update the props typing
to React.ComponentProps<'kbd'> (adjust any consumers if needed), ensuring the
component signature (KbdGroup) and spread {...props} align with the element
type.
_⚠️ Potential issue_ | _🟡 Minor_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail FILE="js/desktop/src/components/ui/kbd.tsx" echo "== File ==" ls -l "$FILE" echo "== Lines 1-120 ==" nl -ba "$FILE" | sed -n '1,120p' ``` Repository: flowy-live/llink Length of output: 235 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Find usages of KbdGroup rg -n --hidden --no-ignore-vcs "\bKbdGroup\b" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 240 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Check if there's another KbdGroup defined elsewhere in repo rg -n --hidden --no-ignore-vcs "function KbdGroup|const KbdGroup|export .*KbdGroup" js || true ``` Repository: flowy-live/llink Length of output: 240 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail FILE="js/desktop/src/components/ui/kbd.tsx" echo "== Lines 1-60 ==" cat -n "$FILE" | sed -n '1,60p' ``` Repository: flowy-live/llink Length of output: 1101 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Usage of the component in TSX/TS rg -n --hidden --no-ignore-vcs "<KbdGroup\\b|KbdGroup\\s*\\(" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 174 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Look for the slot attribute usage to understand expected semantics rg -n --hidden --no-ignore-vcs "data-slot=['\"]kbd-group['\"]" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 128 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Find all usages of KbdGroup (JS/TSX) rg -n --hidden --no-ignore-vcs "\b<KbdGroup\b|\bKbdGroup\s*\(" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 174 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Find all occurrences of the data-slot value to infer expected markup rg -n --hidden --no-ignore-vcs "data-slot=['\"]kbd-group['\"]|kbd-group" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 128 --- </details> **Fix `KbdGroup` element/prop-type mismatch** `KbdGroup` is typed as `React.ComponentProps<'div'>` but renders a `<kbd>`, creating an API contract mismatch for consumers. Render a `<div>` (or change the props typing to `'kbd'` if `<kbd>` is intentional). <details> <summary>Proposed fix</summary> ```diff function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { return ( - <kbd + <div data-slot="kbd-group" className={cn('inline-flex items-center gap-1', className)} {...props} - /> + /> ); } ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { return ( <div data-slot="kbd-group" className={cn('inline-flex items-center gap-1', className)} {...props} /> ); } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/components/ui/kbd.tsx` around lines 16 - 23, KbdGroup is typed as React.ComponentProps<'div'> but returns a <kbd>, causing a prop-type mismatch; fix by making the component's props match the rendered element: either change the render to a <div> in the KbdGroup function or update the props typing to React.ComponentProps<'kbd'> (adjust any consumers if needed), ensuring the component signature (KbdGroup) and spread {...props} align with the element type. ``` </details> <!-- fingerprinting:phantom:poseidon:hawk --> <!-- cr-comment:v1:31d6b9ecbf02c9fde611d95b --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2026-06-11 17:31:12 +00:00

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check keyboard handlers for Esc key in stream navigation/action hooks
rg -n -A5 -B5 "key === 'Escape'" --type=ts -g 'use-stream-*.ts'

Repository: flowy-live/llink

Length of output: 42


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced regions in stream-view.tsx
FILE="js/desktop/src/features/particles/stream-view.tsx"
echo "=== stream-view.tsx: lines 120-160 ==="
nl -ba "$FILE" | sed -n '120,170p'

echo "=== stream-view.tsx: lines 300-360 ==="
nl -ba "$FILE" | sed -n '300,360p'

echo "=== stream-view.tsx: lines 590-630 ==="
nl -ba "$FILE" | sed -n '590,630p'

# Locate the hooks mentioned in the original comment
echo "=== Locate useStreamNavigationKeys / useStreamActionKeys ==="
rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" "js/desktop/src/features/particles" || true
rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" "js/desktop/src" || true

# Search for Escape handler implementations across stream-related hooks/files
echo "=== Search for Escape handlers and navigation behavior ==="
rg -n -S --hidden --no-ignore \
  -e "Escape|Esc" \
  -e "key ===" \
  -e "navigate\(-1\)" \
  -e "navigate\(\`/\\$\\{networkId\\}\\`\)" \
  js/desktop/src/features/particles js/desktop/src | head -n 200

# Specifically search for navigate(-1) usage in stream navigation context
echo "=== Search navigate(-1) in stream-related code ==="
rg -n -S --hidden --no-ignore "navigate\(-1\)" js/desktop/src/features/particles js/desktop/src | head -n 200

# Try to find the hook file names matching use-stream-*.ts mentioned by the original script
echo "=== List files matching use-stream-*.ts ==="
fd -t f "use-stream-*.ts" js/desktop/src || true

Repository: flowy-live/llink

Length of output: 160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="js/desktop/src/features/particles/stream-view.tsx"

echo "=== stream-view.tsx: inspect around ~line 136 (keybinding description) ==="
sed -n '120,170p' "$FILE"

echo
echo "=== stream-view.tsx: inspect around ~line 330 (exit countdown) ==="
sed -n '300,360p' "$FILE"

echo
echo "=== stream-view.tsx: inspect around ~line 610 (KeyHint Esc) ==="
sed -n '590,630p' "$FILE"

echo
echo "=== Find keybinding description text ==="
rg -n --hidden --no-ignore "Back to network|network root|navigate\\(-1\\)|navigate\\(\\`/\\$\\{networkId\\}\\`\\)|Escape|Esc" "$FILE" || true

echo
echo "=== Locate useStreamNavigationKeys / useStreamActionKeys definitions/calls ==="
rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" js/desktop/src/features/particles js/desktop/src || true

echo
echo "=== Search for Escape/Esc key handlers + navigate(...) patterns across stream-related code ==="
rg -n --hidden --no-ignore -S \
  -e "key === 'Escape'|key === \"Escape\"|key === 'Esc'|key === \"Esc\"|Escape|Esc" \
  -e "navigate\\(-1\\)" \
  -e "navigate\\(\\`/\\$\\{networkId\\}\\`\\)" \
  js/desktop/src/features/particles js/desktop/src | head -n 200

echo
echo "=== List files matching use-stream-*.ts (if any) ==="
fd -t f "use-stream-*.ts" js/desktop/src || true

echo
echo "=== List any use-stream* files under particles ==="
fd -t f "use-stream*" js/desktop/src/features/particles || true

Repository: flowy-live/llink

Length of output: 6669


🏁 Script executed:

#!/bin/bash
set -euo pipefail

NAV_KEYS="js/desktop/src/hooks/use-stream-navigation-keys.ts"
ACTION_KEYS="js/desktop/src/hooks/use-stream-action-keys.ts"
STREAM_VIEW="js/desktop/src/features/particles/stream-view.tsx"

echo "=== rg: Escape/Esc in navigation/action hooks ==="
rg -n --hidden --no-ignore -S "Escape|Esc" "$NAV_KEYS" "$ACTION_KEYS" || true

echo
echo "=== rg: navigate(-1) in navigation/action hooks ==="
rg -n --hidden --no-ignore -S "navigate\(-1\)" "$NAV_KEYS" "$ACTION_KEYS" || true

echo
echo "=== rg: navigate(\`/${networkId}\`) in navigation/action hooks ==="
# Use single quotes to avoid backtick command substitution
rg -n --hidden --no-ignore -S 'navigate\(`\/\$\{networkId\}\`\)' "$NAV_KEYS" "$ACTION_KEYS" || true

echo
echo "=== Show navigation hook Escape handler block (lines around match) ==="
rg -n --hidden --no-ignore -S "Escape|Esc" "$NAV_KEYS" -C 8 || true

echo
echo "=== Show action hook Escape handler block (lines around match) ==="
rg -n --hidden --no-ignore -S "Escape|Esc" "$ACTION_KEYS" -C 8 || true

echo
echo "=== Also check stream-view where hooks are used (Esc/keybinding wiring) ==="
rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys|handleExitNavigate|exit countdown|Esc|Escape" "$STREAM_VIEW" -C 3

Repository: flowy-live/llink

Length of output: 512


🏁 Script executed:

#!/bin/bash
set -euo pipefail

NAV_KEYS="js/desktop/src/hooks/use-stream-navigation-keys.ts"
ACTION_KEYS="js/desktop/src/hooks/use-stream-action-keys.ts"
STREAM_VIEW="js/desktop/src/features/particles/stream-view.tsx"

echo "=== stream-navigation-keys.ts (full file if small, else around Escape case) ==="
wc -l "$NAV_KEYS"
sed -n '1,120p' "$NAV_KEYS"

echo
echo "=== stream-action-keys.ts (full file if small, else around Escape case) ==="
wc -l "$ACTION_KEYS"
sed -n '1,160p' "$ACTION_KEYS"

echo
echo "=== stream-view.tsx: where hooks are wired (around lines ~240-320) ==="
sed -n '220,320p' "$STREAM_VIEW"

echo
echo "=== stream-view.tsx: where exit countdown and handleExitNavigate are defined (around ~320-340) ==="
sed -n '320,360p' "$STREAM_VIEW"

echo
echo "=== Find navigate(-1) occurrences in stream-view.tsx ==="
rg -n "navigate\\(-1\\)" "$STREAM_VIEW" || true

echo
echo "=== Find handleExitNavigate / navigate(\\`/\\$\\{networkId\\}\\`) occurrences in stream-view.tsx ==="
rg -n "handleExitNavigate|navigate\\(\\`/\\$\\{networkId\\}\\`\\)" "$STREAM_VIEW" || true

Repository: flowy-live/llink

Length of output: 9501


Fix “Esc / Back to network” navigation to go to the network root

  • KeyHint for Esc calls navigate(-1) (js/desktop/src/features/particles/stream-view.tsx around 610-616), and the useStreamNavigationKeys Escape handler also uses navigate(-1) (js/desktop/src/hooks/use-stream-navigation-keys.ts around case 'Escape').
  • This conflicts with the keybinding/KeyHint copy (“Back to network”) and the exit countdown behavior, which navigates to navigate(\/${networkId}`)(js/desktop/src/features/particles/stream-view.tsx around thehandleExitNavigate`).
  • If the stream page wasn’t reached via history from the network root, Esc may take the user to the wrong place.

Change the Escape click/keyboard handler to navigate(\/${networkId}`)` for consistency with the exit flow, or update the UI copy/title to match “history back”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/desktop/src/features/particles/stream-view.tsx` around lines 610 - 616,
The Escape action currently uses history back (navigate(-1)) which can leave
users off the network root; update the Escape handlers to navigate to the
network root instead: replace navigate(-1) in the KeyHint onClick (KeyHint in
stream-view.tsx) and the Escape case in useStreamNavigationKeys to call
navigate(`/${networkId}`) (use the same networkId used by handleExitNavigate) so
the keybinding, title, and exit countdown all consistently go to the network
root; alternatively, if you prefer history semantics, change the KeyHint
title/text from “Back to network” to “Back”/“History back” instead.

Addressed in commit 1cf144b

_⚠️ Potential issue_ | _🟠 Major_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash # Check keyboard handlers for Esc key in stream navigation/action hooks rg -n -A5 -B5 "key === 'Escape'" --type=ts -g 'use-stream-*.ts' ``` Repository: flowy-live/llink Length of output: 42 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Inspect the referenced regions in stream-view.tsx FILE="js/desktop/src/features/particles/stream-view.tsx" echo "=== stream-view.tsx: lines 120-160 ===" nl -ba "$FILE" | sed -n '120,170p' echo "=== stream-view.tsx: lines 300-360 ===" nl -ba "$FILE" | sed -n '300,360p' echo "=== stream-view.tsx: lines 590-630 ===" nl -ba "$FILE" | sed -n '590,630p' # Locate the hooks mentioned in the original comment echo "=== Locate useStreamNavigationKeys / useStreamActionKeys ===" rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" "js/desktop/src/features/particles" || true rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" "js/desktop/src" || true # Search for Escape handler implementations across stream-related hooks/files echo "=== Search for Escape handlers and navigation behavior ===" rg -n -S --hidden --no-ignore \ -e "Escape|Esc" \ -e "key ===" \ -e "navigate\(-1\)" \ -e "navigate\(\`/\\$\\{networkId\\}\\`\)" \ js/desktop/src/features/particles js/desktop/src | head -n 200 # Specifically search for navigate(-1) usage in stream navigation context echo "=== Search navigate(-1) in stream-related code ===" rg -n -S --hidden --no-ignore "navigate\(-1\)" js/desktop/src/features/particles js/desktop/src | head -n 200 # Try to find the hook file names matching use-stream-*.ts mentioned by the original script echo "=== List files matching use-stream-*.ts ===" fd -t f "use-stream-*.ts" js/desktop/src || true ``` Repository: flowy-live/llink Length of output: 160 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail FILE="js/desktop/src/features/particles/stream-view.tsx" echo "=== stream-view.tsx: inspect around ~line 136 (keybinding description) ===" sed -n '120,170p' "$FILE" echo echo "=== stream-view.tsx: inspect around ~line 330 (exit countdown) ===" sed -n '300,360p' "$FILE" echo echo "=== stream-view.tsx: inspect around ~line 610 (KeyHint Esc) ===" sed -n '590,630p' "$FILE" echo echo "=== Find keybinding description text ===" rg -n --hidden --no-ignore "Back to network|network root|navigate\\(-1\\)|navigate\\(\\`/\\$\\{networkId\\}\\`\\)|Escape|Esc" "$FILE" || true echo echo "=== Locate useStreamNavigationKeys / useStreamActionKeys definitions/calls ===" rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys" js/desktop/src/features/particles js/desktop/src || true echo echo "=== Search for Escape/Esc key handlers + navigate(...) patterns across stream-related code ===" rg -n --hidden --no-ignore -S \ -e "key === 'Escape'|key === \"Escape\"|key === 'Esc'|key === \"Esc\"|Escape|Esc" \ -e "navigate\\(-1\\)" \ -e "navigate\\(\\`/\\$\\{networkId\\}\\`\\)" \ js/desktop/src/features/particles js/desktop/src | head -n 200 echo echo "=== List files matching use-stream-*.ts (if any) ===" fd -t f "use-stream-*.ts" js/desktop/src || true echo echo "=== List any use-stream* files under particles ===" fd -t f "use-stream*" js/desktop/src/features/particles || true ``` Repository: flowy-live/llink Length of output: 6669 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail NAV_KEYS="js/desktop/src/hooks/use-stream-navigation-keys.ts" ACTION_KEYS="js/desktop/src/hooks/use-stream-action-keys.ts" STREAM_VIEW="js/desktop/src/features/particles/stream-view.tsx" echo "=== rg: Escape/Esc in navigation/action hooks ===" rg -n --hidden --no-ignore -S "Escape|Esc" "$NAV_KEYS" "$ACTION_KEYS" || true echo echo "=== rg: navigate(-1) in navigation/action hooks ===" rg -n --hidden --no-ignore -S "navigate\(-1\)" "$NAV_KEYS" "$ACTION_KEYS" || true echo echo "=== rg: navigate(\`/${networkId}\`) in navigation/action hooks ===" # Use single quotes to avoid backtick command substitution rg -n --hidden --no-ignore -S 'navigate\(`\/\$\{networkId\}\`\)' "$NAV_KEYS" "$ACTION_KEYS" || true echo echo "=== Show navigation hook Escape handler block (lines around match) ===" rg -n --hidden --no-ignore -S "Escape|Esc" "$NAV_KEYS" -C 8 || true echo echo "=== Show action hook Escape handler block (lines around match) ===" rg -n --hidden --no-ignore -S "Escape|Esc" "$ACTION_KEYS" -C 8 || true echo echo "=== Also check stream-view where hooks are used (Esc/keybinding wiring) ===" rg -n --hidden --no-ignore -S "useStreamNavigationKeys|useStreamActionKeys|handleExitNavigate|exit countdown|Esc|Escape" "$STREAM_VIEW" -C 3 ``` Repository: flowy-live/llink Length of output: 512 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail NAV_KEYS="js/desktop/src/hooks/use-stream-navigation-keys.ts" ACTION_KEYS="js/desktop/src/hooks/use-stream-action-keys.ts" STREAM_VIEW="js/desktop/src/features/particles/stream-view.tsx" echo "=== stream-navigation-keys.ts (full file if small, else around Escape case) ===" wc -l "$NAV_KEYS" sed -n '1,120p' "$NAV_KEYS" echo echo "=== stream-action-keys.ts (full file if small, else around Escape case) ===" wc -l "$ACTION_KEYS" sed -n '1,160p' "$ACTION_KEYS" echo echo "=== stream-view.tsx: where hooks are wired (around lines ~240-320) ===" sed -n '220,320p' "$STREAM_VIEW" echo echo "=== stream-view.tsx: where exit countdown and handleExitNavigate are defined (around ~320-340) ===" sed -n '320,360p' "$STREAM_VIEW" echo echo "=== Find navigate(-1) occurrences in stream-view.tsx ===" rg -n "navigate\\(-1\\)" "$STREAM_VIEW" || true echo echo "=== Find handleExitNavigate / navigate(\\`/\\$\\{networkId\\}\\`) occurrences in stream-view.tsx ===" rg -n "handleExitNavigate|navigate\\(\\`/\\$\\{networkId\\}\\`\\)" "$STREAM_VIEW" || true ``` Repository: flowy-live/llink Length of output: 9501 --- </details> **Fix “Esc / Back to network” navigation to go to the network root** - `KeyHint` for `Esc` calls `navigate(-1)` (js/desktop/src/features/particles/stream-view.tsx around 610-616), and the `useStreamNavigationKeys` Escape handler also uses `navigate(-1)` (js/desktop/src/hooks/use-stream-navigation-keys.ts around case `'Escape'`). - This conflicts with the keybinding/KeyHint copy (“Back to network”) and the exit countdown behavior, which navigates to `navigate(\`/${networkId}\`)` (js/desktop/src/features/particles/stream-view.tsx around the `handleExitNavigate`). - If the stream page wasn’t reached via history from the network root, `Esc` may take the user to the wrong place. Change the Escape click/keyboard handler to `navigate(\`/${networkId}\`)` for consistency with the exit flow, or update the UI copy/title to match “history back”. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/features/particles/stream-view.tsx` around lines 610 - 616, The Escape action currently uses history back (navigate(-1)) which can leave users off the network root; update the Escape handlers to navigate to the network root instead: replace navigate(-1) in the KeyHint onClick (KeyHint in stream-view.tsx) and the Escape case in useStreamNavigationKeys to call navigate(`/${networkId}`) (use the same networkId used by handleExitNavigate) so the keybinding, title, and exit countdown all consistently go to the network root; alternatively, if you prefer history semantics, change the KeyHint title/text from “Back to network” to “Back”/“History back” instead. ``` </details> <!-- fingerprinting:phantom:poseidon:puma --> <!-- cr-comment:v1:0c591c011a7d40bc50e15196 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 1cf144b
Sign in to join this conversation.