Skip to content
Merged
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
69 changes: 69 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Checks

on:
pull_request:
push:
branches:
- main

jobs:
frontend:
name: Frontend checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- run: corepack enable && corepack install

- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: yarn

- run: yarn install --frozen-lockfile --immutable

- name: Typecheck
run: yarn typecheck

- name: Lint
run: yarn lint

- name: Test
run: yarn test

rust:
name: Rust checks
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils

- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'

- run: corepack enable && corepack install

- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: yarn

- run: yarn install --frozen-lockfile --immutable

- name: Format
run: yarn rust:fmt

- name: Clippy
run: yarn rust:clippy

- name: Check
run: yarn rust:check
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Agents

## Rust

- When writing `#[cfg]`-gated code, proactively add `#[allow(unused_variables)]` if parameters are only used in some platform branches
- Always run `yarn rust:clippy` (not just `cargo check`) as part of local verification
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See: @AGENTS.md
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
"tauri": "tauri",
"typecheck": "tsc --noEmit",
"lint": "oxlint src --deny-warnings",
"test": "vitest run --passWithNoTests",
"rust:check": "cargo check --manifest-path src-tauri/Cargo.toml",
"rust:clippy": "cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings",
"rust:fmt": "cargo fmt --manifest-path src-tauri/Cargo.toml --check"
},
"dependencies": {
"@tauri-apps/api": "^2",
Expand All @@ -23,9 +29,11 @@
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"oxlint": "^1.43.0",
"tailwindcss": "^4",
"typescript": "~5.8.3",
"vite": "^7.0.4"
"vite": "^7.0.4",
"vitest": "^4.0.18"
},
"volta": {
"node": "22.17.1",
Expand Down
16 changes: 4 additions & 12 deletions src-tauri/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,7 @@ pub async fn auth_device_poll(
expires_in: u64,
state: tauri::State<'_, Arc<AuthState>>,
) -> Result<AuthStatusResponse, String> {
let deadline =
tokio::time::Instant::now() + tokio::time::Duration::from_secs(expires_in);
let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(expires_in);
let poll_interval = tokio::time::Duration::from_secs(interval.max(5));

loop {
Expand All @@ -259,18 +258,14 @@ pub async fn auth_device_poll(
("client_id", CLIENT_ID),
("scopes", SCOPES),
("device_code", device_code.as_str()),
(
"grant_type",
"urn:ietf:params:oauth:grant-type:device_code",
),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
])
.send()
.await
.map_err(|e| e.to_string())?;

if resp.status().is_success() {
let tokens: TwitchTokenResponse =
resp.json().await.map_err(|e| e.to_string())?;
let tokens: TwitchTokenResponse = resp.json().await.map_err(|e| e.to_string())?;
info!("[auth] device flow got tokens, validating…");
let username = validate_token(&state.http, &tokens.access_token).await?;
let now = chrono::Utc::now().timestamp();
Expand Down Expand Up @@ -340,10 +335,7 @@ pub async fn auth_logout(state: tauri::State<'_, Arc<AuthState>>) -> Result<(),
let _ = state
.http
.post("https://id.twitch.tv/oauth2/revoke")
.form(&[
("client_id", CLIENT_ID),
("token", &data.access_token),
])
.form(&[("client_id", CLIENT_ID), ("token", &data.access_token)])
.send()
.await;
}
Expand Down
7 changes: 4 additions & 3 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ fn set_ignore_cursor(window: tauri::WebviewWindow, ignore: bool) -> Result<(), S
}

#[tauri::command]
#[allow(unused_variables)]
fn get_cursor_position(window: tauri::WebviewWindow) -> Result<(f64, f64), String> {
#[cfg(target_os = "windows")]
{
#[repr(C)]
struct POINT {
struct Point {
x: i32,
y: i32,
}
extern "system" {
fn GetCursorPos(lp_point: *mut POINT) -> i32;
fn GetCursorPos(lp_point: *mut Point) -> i32;
}
let mut point = POINT { x: 0, y: 0 };
let mut point = Point { x: 0, y: 0 };
if unsafe { GetCursorPos(&mut point) } == 0 {
return Err("GetCursorPos failed".into());
}
Expand Down
20 changes: 16 additions & 4 deletions src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,18 @@ pub fn write_settings(data: Value, state: tauri::State<'_, SettingsState>) -> Re
pub fn read_chat_history(state: tauri::State<'_, SettingsState>) -> Result<Option<Value>, String> {
match std::fs::read_to_string(&state.chat_history_path) {
Ok(json) => {
info!("[chat-history] loaded from {}", state.chat_history_path.display());
info!(
"[chat-history] loaded from {}",
state.chat_history_path.display()
);
let data: Value = serde_json::from_str(&json).map_err(|e| e.to_string())?;
Ok(Some(data))
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
info!("[chat-history] no file at {}", state.chat_history_path.display());
info!(
"[chat-history] no file at {}",
state.chat_history_path.display()
);
Ok(None)
}
Err(e) => {
Expand All @@ -70,7 +76,10 @@ pub fn read_chat_history(state: tauri::State<'_, SettingsState>) -> Result<Optio
}

#[tauri::command]
pub fn write_chat_history(data: Value, state: tauri::State<'_, SettingsState>) -> Result<(), String> {
pub fn write_chat_history(
data: Value,
state: tauri::State<'_, SettingsState>,
) -> Result<(), String> {
if let Some(parent) = state.chat_history_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
Expand All @@ -79,6 +88,9 @@ pub fn write_chat_history(data: Value, state: tauri::State<'_, SettingsState>) -
error!("[chat-history] write error: {e}");
e.to_string()
})?;
info!("[chat-history] saved to {}", state.chat_history_path.display());
info!(
"[chat-history] saved to {}",
state.chat_history_path.display()
);
Ok(())
}
Loading