fix lint: make bun tsc --noEmit actually pass (was a TS crash + latent errors)
tsconfig used jsx:"preserve"+jsxImportSource, which trips a TS 5.9
internal crash ("Expected sourceFile.imports[0] to be the synthesized JSX
runtime import") so tsc could never run clean. Switch to jsx:"react-jsx"
with the package's real jsx-runtime types, and fix the real errors that
surfaced:
- delete dead src/components/Navigation.tsx (imported ./Tab that doesn't
exist; the component has no importers)
- PlaybackControls: fix relative path to @/utils/audio-player
- SourceBadge: drop dead module-level typeColor (bare 'theme')
- command palette: bind to the real 'command' keybind (:) instead of the
never-defined 'command_list' action (palette was unreachable)
- yazi-pane-row test: destroy() must return Promise<void> as typed
Also fold in the package.json lint fix (bun tsc --noEmit) and doc polish.
This commit is contained in:
@@ -23,8 +23,8 @@ make native # build libcavacore.dylib from the vendored C source
|
||||
bun run dev # launch with hot reload (alias: make dev)
|
||||
```
|
||||
|
||||
The app is a TUI — it expects a real terminal (kitty, iTerm2, WezTerm, tmux,
|
||||
…). It will not render in a plain captured `bash` session.
|
||||
The app is a TUI — it expects a real terminal (Ghostty, kitty, iTerm2,
|
||||
WezTerm, tmux, …). It will not render in a plain captured `bash` session.
|
||||
|
||||
## What each command does
|
||||
|
||||
@@ -35,14 +35,11 @@ The app is a TUI — it expects a real terminal (kitty, iTerm2, WezTerm, tmux,
|
||||
| `bun run dev` | Run with hot reload |
|
||||
| `bun run start` | Run once (no watch) |
|
||||
| `bun test` | Run the test suite (see [Testing](#testing)) |
|
||||
| `make lint` | Type-check with `bun tsc --noEmit` |
|
||||
| `bun run lint` | Type-check |
|
||||
| `bun run build` | Bundle JS into `dist/` + copy native libs (the `podtui` npm script path) |
|
||||
| `make dist` | Compile the standalone binary + make the current platform's tarball |
|
||||
| `make clean` | Remove `dist/` |
|
||||
|
||||
> Note: `package.json` also has a `lint` script that points at a
|
||||
> `lint.ts` file that doesn't exist. Use `make lint` (real type-checking).
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
@@ -112,10 +109,6 @@ Cavacore smoke test: `bun tests/cavacore-smoke.ts`
|
||||
`-headerpad`” for a prebuilt dylib. The app dlopens the libs by path, so
|
||||
the warning is cosmetic; installs complete and the app boots.
|
||||
|
||||
4. **`make lint` is the truth, not the `package.json` scripts.**
|
||||
The repo's ESLint wiring is stale; `make lint` runs the real
|
||||
type-check and is what CI treats as the clean bar.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
@@ -135,7 +128,7 @@ Audio is a no-op during those snapshots. The last frame lands in
|
||||
|
||||
## Releasing
|
||||
|
||||
Releases are built and published from **tags**; CI does the heavy lifting.
|
||||
Releases are built and published from **tags**
|
||||
|
||||
### Steps
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"build": "bun run build.ts",
|
||||
"dist": "bun dist/index.js",
|
||||
"test": "bun test",
|
||||
"lint": "bun run lint.ts"
|
||||
"lint": "bun tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { TabId } from "./Tab"
|
||||
import { useTheme } from "@/context/ThemeContext"
|
||||
|
||||
type NavigationProps = {
|
||||
activeTab: TabId
|
||||
onTabSelect: (tab: TabId) => void
|
||||
}
|
||||
|
||||
export function Navigation(props: NavigationProps) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<box style={{ flexDirection: "row", width: "100%", height: 1 }}>
|
||||
<text fg={theme.text}>
|
||||
{props.activeTab === "feed" ? "[" : " "}Feed{props.activeTab === "feed" ? "]" : " "}
|
||||
<span> </span>
|
||||
{props.activeTab === "shows" ? "[" : " "}My Shows{props.activeTab === "shows" ? "]" : " "}
|
||||
<span> </span>
|
||||
{props.activeTab === "discover" ? "[" : " "}Discover{props.activeTab === "discover" ? "]" : " "}
|
||||
<span> </span>
|
||||
{props.activeTab === "search" ? "[" : " "}Search{props.activeTab === "search" ? "]" : " "}
|
||||
<span> </span>
|
||||
{props.activeTab === "player" ? "[" : " "}Player{props.activeTab === "player" ? "]" : " "}
|
||||
<span> </span>
|
||||
{props.activeTab === "settings" ? "[" : " "}Settings{props.activeTab === "settings" ? "]" : " "}
|
||||
</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { BackendName } from "../utils/audio-player"
|
||||
import { useTheme } from "@/context/ThemeContext"
|
||||
import type { BackendName } from "@/utils/audio-player";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
|
||||
type PlaybackControlsProps = {
|
||||
isPlaying: boolean
|
||||
volume: number
|
||||
speed: number
|
||||
backendName?: BackendName
|
||||
hasAudioUrl?: boolean
|
||||
onToggle: () => void
|
||||
onPrev: () => void
|
||||
onNext: () => void
|
||||
onVolumeChange: (value: number) => void
|
||||
onSpeedChange: (value: number) => void
|
||||
}
|
||||
isPlaying: boolean;
|
||||
volume: number;
|
||||
speed: number;
|
||||
backendName?: BackendName;
|
||||
hasAudioUrl?: boolean;
|
||||
onToggle: () => void;
|
||||
onPrev: () => void;
|
||||
onNext: () => void;
|
||||
onVolumeChange: (value: number) => void;
|
||||
onSpeedChange: (value: number) => void;
|
||||
};
|
||||
|
||||
const BACKEND_LABELS: Record<BackendName, string> = {
|
||||
mpv: "mpv",
|
||||
@@ -20,19 +20,41 @@ const BACKEND_LABELS: Record<BackendName, string> = {
|
||||
afplay: "afplay",
|
||||
system: "system",
|
||||
none: "none",
|
||||
}
|
||||
};
|
||||
|
||||
export function PlaybackControls(props: PlaybackControlsProps) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<box flexDirection="row" gap={1} alignItems="center" border padding={1} borderColor={theme.border}>
|
||||
<box border padding={0} onMouseDown={props.onPrev} borderColor={theme.border}>
|
||||
<box
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
alignItems="center"
|
||||
border
|
||||
padding={1}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<box
|
||||
border
|
||||
padding={0}
|
||||
onMouseDown={props.onPrev}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<text fg={theme.primary}>[Prev]</text>
|
||||
</box>
|
||||
<box border padding={0} onMouseDown={props.onToggle} borderColor={theme.border}>
|
||||
<box
|
||||
border
|
||||
padding={0}
|
||||
onMouseDown={props.onToggle}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<text fg={theme.primary}>{props.isPlaying ? "[Pause]" : "[Play]"}</text>
|
||||
</box>
|
||||
<box border padding={0} onMouseDown={props.onNext} borderColor={theme.border}>
|
||||
<box
|
||||
border
|
||||
padding={0}
|
||||
onMouseDown={props.onNext}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<text fg={theme.primary}>[Next]</text>
|
||||
</box>
|
||||
<box flexDirection="row" gap={1} marginLeft={2}>
|
||||
@@ -60,5 +82,5 @@ export function PlaybackControls(props: PlaybackControlsProps) {
|
||||
</box>
|
||||
)}
|
||||
</box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,8 @@ const typeLabel = (sourceType?: SourceType) => {
|
||||
return "Source";
|
||||
};
|
||||
|
||||
const typeColor = (sourceType?: SourceType) => {
|
||||
if (sourceType === SourceType.API) return theme.primary;
|
||||
if (sourceType === SourceType.RSS) return theme.success;
|
||||
if (sourceType === SourceType.CUSTOM) return theme.warning;
|
||||
return theme.textMuted;
|
||||
};
|
||||
|
||||
// No module-level typeColor here — it needs the theme from the component.
|
||||
// The correct definition lives inside SourceBadge below.
|
||||
export function SourceBadge(props: SourceBadgeProps) {
|
||||
const { theme } = useTheme();
|
||||
const label = () => props.sourceName || props.sourceId;
|
||||
|
||||
@@ -180,12 +180,14 @@ export function CommandProvider(props: ParentProps) {
|
||||
const dialog = useDialog();
|
||||
const keybind = useKeybinds();
|
||||
|
||||
// Open command palette on ctrl+p or command_list keybind
|
||||
// Open the command palette via the `command` keybind (bound to `:` in
|
||||
// keybinds.jsonc). The old hardcoded "command_list" name was never a
|
||||
// canonical action, so the palette was unreachable dead code.
|
||||
useKeyboard((evt) => {
|
||||
if (value.suspended()) return;
|
||||
if (dialog.isOpen) return;
|
||||
if (evt.defaultPrevented) return;
|
||||
if (keybind.match("command_list", evt)) {
|
||||
if (keybind.match("command", evt)) {
|
||||
evt.preventDefault();
|
||||
value.show();
|
||||
return;
|
||||
@@ -279,7 +281,11 @@ function CommandDialog(props: {
|
||||
</box>
|
||||
|
||||
{/* Command list */}
|
||||
<box flexDirection="column" maxHeight={maxHeight} borderColor={theme.border}>
|
||||
<box
|
||||
flexDirection="column"
|
||||
maxHeight={maxHeight}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<For each={filteredOptions().slice(0, 10)}>
|
||||
{(option, index) => (
|
||||
<SelectableBox
|
||||
|
||||
@@ -106,7 +106,12 @@ async function renderPaneRow(props: TestPaneProps): Promise<{
|
||||
await new Promise((r) => setTimeout(r, 40));
|
||||
}
|
||||
const spans = setup.captureSpans() as unknown as Frame;
|
||||
return { spans, destroy: () => setup.renderer.destroy() };
|
||||
return {
|
||||
spans,
|
||||
destroy: async () => {
|
||||
setup.renderer.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const cleanups: (() => void | Promise<void>)[] = [];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@opentui/solid",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
Reference in New Issue
Block a user