Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bun run lint # ESLint for frontend
bun run lint:fix # ESLint with auto-fix
bun run format # Prettier + cargo fmt
bun run format:check # Check formatting without changes
bun run storybook # Preview UI components in isolation (http://localhost:1422)
```

**Model Setup (Required for Development):**
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ The process is entirely local:

For detailed build instructions including platform-specific requirements, see [BUILD.md](BUILD.md).

To preview UI components in isolation, run the component storybook:

```bash
bun run storybook
```

Then open [http://localhost:1422](http://localhost:1422) in your browser.

## Architecture

Handy is built as a Tauri application combining:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"format:backend": "cd src-tauri && cargo fmt",
"test:playwright": "playwright test",
"test:playwright:ui": "playwright test --ui",
"check:translations": "bun scripts/check-translations.ts"
"check:translations": "bun scripts/check-translations.ts",
"storybook": "vite --config vite.storybook.config.ts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README.md and CLAUDE.md should be updated to document this new command and how to use the storybook. right now the only mention is in the PR description

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the bun run storybook command to both README.md and CLAUDE.md with a short description of what it does and the URL to open.

},
"dependencies": {
"@tailwindcss/vite": "^4.1.16",
Expand Down
1 change: 1 addition & 0 deletions storybook/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vite/
42 changes: 42 additions & 0 deletions storybook/index.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this references http://localhost:1422/HandyMain/storybook/ but the actual URL is http://localhost:1422/

minor but would confuse someone opening the html file directly

this will anyway get removed once we use storybook properly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! The URL now correctly points to http://localhost:1422/

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Handy Storybook</title>
</head>
<body>
<div id="root">
<div
style="
max-width: 640px;
margin: 48px auto;
padding: 16px;
border: 1px solid #d9d9d9;
border-radius: 12px;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
line-height: 1.5;
"
>
<h1 style="margin: 0 0 8px; font-size: 20px">Handy Storybook</h1>
<p style="margin: 0 0 8px">
This page must be served with Vite. Opening this file directly will
not render React components.
</p>
<p style="margin: 0 0 8px">Run:</p>
<pre
style="
margin: 0;
padding: 10px;
background: #f6f6f6;
border-radius: 8px;
overflow: auto;
"
><code>bun install
bun run storybook</code></pre>
<p style="margin: 8px 0 0">Then open: <code>http://localhost:1422/</code></p>
</div>
</div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions storybook/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "@/i18n";
import "./storybook.css";
import { StorybookApp } from "./storybook";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<StorybookApp />
</React.StrictMode>,
);
85 changes: 85 additions & 0 deletions storybook/mocks/bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
MOCK_APP_DIR,
MOCK_AUDIO_FILE,
MOCK_HISTORY,
MOCK_LOG_DIR,
MOCK_MODELS,
MOCK_POST_PROCESS_PROVIDERS,
MOCK_PROMPTS,
MOCK_RECORDINGS_DIR,
MOCK_SETTINGS,
} from "./data";

const ok = <T>(data: T) => ({ status: "ok" as const, data });

export const commands = {
// App + system
getAppSettings: async () => ok(MOCK_SETTINGS),
getDefaultSettings: async () => ok(MOCK_SETTINGS),
getAppDirPath: async () => ok(MOCK_APP_DIR),
openAppDataDir: async () => ok(null),
getLogDirPath: async () => ok(MOCK_LOG_DIR),
openLogDir: async () => ok(null),

// Permissions + shortcuts
initializeEnigo: async () => ok(null),
initializeShortcuts: async () => ok(null),
suspendBinding: async () => ok(null),
resumeBinding: async () => ok(null),
startHandyKeysRecording: async () => ok(null),
stopHandyKeysRecording: async () => ok(null),
changeKeyboardImplementationSetting: async () =>
ok({ success: true, reset_bindings: [] }),

// Models
getTranscriptionModelStatus: async () => ok(MOCK_SETTINGS.selected_model),
isRecording: async () => false,

// Hardware + environment
isLaptop: async () => ok(true),

// Post-processing
addPostProcessPrompt: async (name: string, prompt: string) =>
ok({ id: `prompt_${Date.now()}`, name, prompt }),
updatePostProcessPrompt: async () => ok(null),
deletePostProcessPrompt: async () => ok(null),
checkAppleIntelligenceAvailable: async () => false,

// History
getHistoryEntries: async () => ok(MOCK_HISTORY),
toggleHistoryEntrySaved: async () => ok(null),
getAudioFilePath: async () => ok(MOCK_AUDIO_FILE),
deleteHistoryEntry: async () => ok(null),
openRecordingsFolder: async () => ok(MOCK_RECORDINGS_DIR),

// Misc settings
setModelUnloadTimeout: async () => ok(null),

// Models list for storybook previews (not used by components directly)
getAvailableModels: async () => ok(MOCK_MODELS),

// Provider data for storybook previews (not used by components directly)
getPostProcessProviders: async () => ok(MOCK_POST_PROCESS_PROVIDERS),
getPostProcessPrompts: async () => ok(MOCK_PROMPTS),
};

export type {
AppSettings,
AudioDevice,
ClipboardHandling,
EngineType,
HistoryEntry,
ImplementationChangeResult,
KeyboardImplementation,
LLMPrompt,
LogLevel,
ModelInfo,
ModelUnloadTimeout,
OverlayPosition,
PasteMethod,
PostProcessProvider,
RecordingRetentionPeriod,
Result,
ShortcutBinding,
SoundTheme,
} from "../../../src/bindings";
Loading