From 340b094c75afbbf33e27c035e8765337c3522e01 Mon Sep 17 00:00:00 2001 From: Gabriel Gordon-Hall Date: Fri, 27 Jun 2025 13:32:32 +0100 Subject: [PATCH] chore: setup CI scripts (#6) * wip: workflows * wip: fix up issues in ci scripts and fix frontend lint errors * wip: fix backend lints * remove unused deps * wip: build frontend in test.yml * wip: attempt to improve Rust caching * wip: testing release * wip: linear release flow * wip: check against both package.json versions * wip: spurious attempt to get Rust caching * wip: more cache * merge release and publish jobs; add more caching to release flow * decouple github releases and npm publishing * update pack flow --------- Co-authored-by: couscous --- .github/actions/setup-node/action.yml | 29 +++ .github/workflows/pre-release.yml | 136 ++++++++++ .github/workflows/publish.yml | 114 +++++++++ .github/workflows/test.yml | 57 +++++ Cargo.toml | 2 - backend/Cargo.toml | 8 +- backend/src/app_state.rs | 2 +- backend/src/execution_monitor.rs | 6 +- backend/src/executor.rs | 19 -- backend/src/executors/amp.rs | 4 +- backend/src/executors/claude.rs | 4 +- backend/src/executors/gemini.rs | 4 +- backend/src/main.rs | 2 +- backend/src/models/config.rs | 12 + backend/src/models/execution_process.rs | 2 + backend/src/models/executor_session.rs | 5 + backend/src/models/task_attempt.rs | 3 +- backend/src/models/task_attempt_activity.rs | 2 + backend/src/routes/filesystem.rs | 44 ++-- frontend/package.json | 4 +- .../components/projects/project-detail.tsx | 8 +- .../tasks/ExecutionOutputViewer.tsx | 12 +- .../components/tasks/TaskActivityHistory.tsx | 4 +- .../components/tasks/TaskDetailsHeader.tsx | 2 +- .../src/components/tasks/TaskDetailsPanel.tsx | 6 +- .../components/tasks/TaskDetailsToolbar.tsx | 23 +- .../components/tasks/TaskFollowUpSection.tsx | 4 +- .../src/components/tasks/TaskFormDialog.tsx | 17 +- frontend/src/hooks/useTaskDetails.ts | 240 +++++++++--------- frontend/src/pages/project-tasks.tsx | 93 +++---- frontend/src/pages/task-attempt-compare.tsx | 25 +- npx-cli/package.json | 4 +- package.json | 3 +- 33 files changed, 620 insertions(+), 280 deletions(-) create mode 100644 .github/actions/setup-node/action.yml create mode 100644 .github/workflows/pre-release.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 00000000..5c8b2b12 --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,29 @@ +name: 'Setup Node.js and pnpm' +description: 'Sets up Node.js and pnpm with caching' + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: 'https://registry.npmjs.org' + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: ${{ env.PNPM_VERSION }} + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 00000000..fb1e455d --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,136 @@ +# To pre-release: +# ``` +# git tag -a v0.1.0 -m "Release 0.1.0" +# git push origin v0.1.0 +# ``` + +name: Create GitHub Pre-Release + +on: + push: + tags: + - "v*.*.*" + +concurrency: + group: release + cancel-in-progress: true + +permissions: + contents: write + packages: write + pull-requests: write + +env: + NODE_VERSION: 22 + PNPM_VERSION: 10.8.1 + TAG_REGEX: '^v[0-9]+\.[0-9]+\.[0-9]+$' + +jobs: + tag-check: + runs-on: ubuntu-latest + if: github.ref_type == 'tag' + steps: + - uses: actions/checkout@v4 + + - name: Validate tag matches package.json version + shell: bash + run: | + set -euo pipefail + echo "::group::Tag validation" + + # 1. Must be a tag and match the regex + [[ "${GITHUB_REF_TYPE}" == "tag" ]] \ + || { echo "❌ Not a tag push"; exit 1; } + [[ "${GITHUB_REF_NAME}" =~ ${TAG_REGEX} ]] \ + || { echo "❌ Tag '${GITHUB_REF_NAME}' != ${TAG_REGEX}"; exit 1; } + + # 2. Extract versions + tag_ver="${GITHUB_REF_NAME#v}" + package_ver="$(node -p "require('./package.json').version")" + cli_package_ver="$(node -p "require('./npx-cli/package.json').version")" + + # 3. Compare + [[ "${tag_ver}" == "${package_ver}" ]] \ + || { echo "❌ Tag ${tag_ver} ≠ package.json ${package_ver}"; exit 1; } + + # 4. Compare tag with npx-cli package.json + [[ "${tag_ver}" == "${cli_package_ver}" ]] \ + || { echo "❌ Tag ${tag_ver} ≠ npx-cli package.json ${cli_package_ver}"; exit 1; } + + echo "✅ Tag and package.json agree (${tag_ver})" + echo "::endgroup::" + + create-prerelease: + needs: tag-check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/setup-node + + - name: Install dependencies + run: pnpm install + + - name: Lint frontend + run: cd frontend && npm run lint + + - name: Type check frontend + run: cd frontend && npx tsc --noEmit + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2025-05-18 + components: rustfmt, clippy + + - name: Build frontend + run: npm run frontend:build + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + env: + RUST_CACHE_DEBUG: true + with: + workspaces: "backend" + + - name: Checks + run: | + cargo fmt --all -- --check + cargo test --workspace + cargo clippy --all --all-targets --all-features -- -D warnings + + - name: Build backend + run: cargo build --release --manifest-path backend/Cargo.toml + + - name: Get version from tag + id: get-version + run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT + + - name: Zip backend + run: | + mkdir vibe-kanban-${{ github.ref_name }} + mv target/release/vibe-kanban vibe-kanban-${{ github.ref_name }} + mv frontend/dist vibe-kanban-${{ github.ref_name }} + zip -r vibe-kanban-${{ github.ref_name }}.zip vibe-kanban-${{ github.ref_name }} + + - name: Code sign + run: echo "pass" + + - name: Pack + run: | + mkdir -p npx-cli/dist/linux-x64 + mv vibe-kanban-${{ github.ref_name }}.zip npx-cli/dist/linux-x64 + cd npx-cli + npm pack + + - name: Create GitHub Pre-Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: Pre-release ${{ github.ref_name }} + prerelease: true + generate_release_notes: true + files: | + vibe-kanban-${{ github.ref_name }}.zip + npx-cli/vibe-kanban-*.tgz \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..03f9f8c3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,114 @@ +name: Publish to npm + +on: + release: + types: [released] + +concurrency: + group: publish + cancel-in-progress: true + +permissions: + contents: read + packages: write + +env: + NODE_VERSION: 22 + PNPM_VERSION: 10.8.1 + +jobs: + publish: + runs-on: ubuntu-latest + # Only run if this was converted from a pre-release + if: github.event.release.prerelease == false + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Setup Node + uses: ./.github/actions/setup-node + + - name: Configure npm authentication + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + + - name: Download release assets + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // Get the release assets + const release = await github.rest.repos.getRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: context.payload.release.id + }); + + // Find the .tgz file + const tgzAsset = release.data.assets.find(asset => asset.name.endsWith('.tgz')); + + if (!tgzAsset) { + core.setFailed('No .tgz file found in release assets'); + return; + } + + // Download the asset + const response = await github.rest.repos.getReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + asset_id: tgzAsset.id, + headers: { + Accept: 'application/octet-stream' + } + }); + + // Save to npx-cli directory + const filePath = path.join('npx-cli', tgzAsset.name); + fs.writeFileSync(filePath, Buffer.from(response.data)); + + console.log(`Downloaded ${tgzAsset.name} to ${filePath}`); + + // Set output for next step + core.setOutput('package-file', filePath); + core.setOutput('package-name', tgzAsset.name); + + - name: Verify package integrity + id: verify + run: | + cd npx-cli + + # List files to confirm download + ls -la *.tgz + + # Verify the package can be read + npm pack --dry-run || echo "Note: This is expected to show differences since we're using the pre-built package" + + # Extract package name from the downloaded file + PACKAGE_FILE=$(ls *.tgz | head -n1) + echo "package-file=$PACKAGE_FILE" >> $GITHUB_OUTPUT + + - name: Publish to npm + run: | + cd npx-cli + + # Publish the exact same package that was tested + PACKAGE_FILE="${{ steps.verify.outputs.package-file }}" + + echo "Publishing $PACKAGE_FILE to npm..." + npm publish "$PACKAGE_FILE" + + echo "✅ Successfully published to npm!" + + - name: Update release description + uses: actions/github-script@v7 + with: + script: | + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: context.payload.release.id, + body: context.payload.release.body + '\n\n✅ **Published to npm registry**' + }); \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..4c30913b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +name: Test + +on: + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +env: + CARGO_TERM_COLOR: always + NODE_VERSION: 22 + PNPM_VERSION: 10.8.1 + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/setup-node + + - name: Install dependencies + run: pnpm install + + - name: Lint frontend + run: cd frontend && npm run lint + + - name: Format check frontend + run: cd frontend && npm run format:check + + - name: Type check frontend + run: cd frontend && npx tsc --noEmit + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2025-05-18 + components: rustfmt, clippy + + - name: Build frontend + run: cd frontend && npm run build + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + env: + RUST_CACHE_DEBUG: true + with: + workspaces: "backend" + + - name: Checks + run: | + cargo fmt --all -- --check + cargo test --workspace + cargo clippy --all --all-targets --all-features -- -D warnings \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2011811a..1a117c4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,10 @@ members = ["backend"] [workspace.dependencies] tokio = { version = "1.0", features = ["full"] } axum = { version = "0.7", features = ["macros"] } -tower = "0.4" tower-http = { version = "0.5", features = ["cors"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" anyhow = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -openssl = { version = "0.10", features = ["vendored"] } openssl-sys = { version = "0.9", features = ["vendored"] } \ No newline at end of file diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 317f80ee..bc33f637 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -8,10 +8,12 @@ default-run = "vibe-kanban" name = "vibe_kanban" path = "src/lib.rs" +[lints.clippy] +uninlined-format-args = "allow" + [dependencies] tokio = { workspace = true } axum = { workspace = true } -tower = { workspace = true } tower-http = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -21,20 +23,16 @@ tracing-subscriber = { workspace = true } sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "chrono", "uuid"] } chrono = { version = "0.4", features = ["serde"] } uuid = { version = "1.0", features = ["v4", "serde"] } -bcrypt = "0.15" -jsonwebtoken = "9.2" ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] } dirs = "5.0" git2 = "0.18" async-trait = "0.1" -dissimilar = "1.0" libc = "0.2" rust-embed = "8.2" mime_guess = "2.0" directories = "6.0.0" open = "5.3.2" ignore = "0.4" -openssl = { workspace = true } openssl-sys = { workspace = true } [build-dependencies] diff --git a/backend/src/app_state.rs b/backend/src/app_state.rs index d6edd597..7d3f625d 100644 --- a/backend/src/app_state.rs +++ b/backend/src/app_state.rs @@ -13,7 +13,7 @@ pub enum ExecutionType { #[derive(Debug)] pub struct RunningExecution { pub task_attempt_id: Uuid, - pub execution_type: ExecutionType, + pub _execution_type: ExecutionType, pub child: tokio::process::Child, } diff --git a/backend/src/execution_monitor.rs b/backend/src/execution_monitor.rs index 67bffb5d..c6fe1ea4 100644 --- a/backend/src/execution_monitor.rs +++ b/backend/src/execution_monitor.rs @@ -81,14 +81,16 @@ async fn play_sound_notification(sound_file: &crate::models::config::SoundFile) } else if cfg!(target_os = "linux") { // Try different Linux notification sounds let sound_path = sound_file.to_path(); - if let Ok(_) = tokio::process::Command::new("paplay") + if tokio::process::Command::new("paplay") .arg(&sound_path) .spawn() + .is_ok() { // Success with paplay - } else if let Ok(_) = tokio::process::Command::new("aplay") + } else if tokio::process::Command::new("aplay") .arg(&sound_path) .spawn() + .is_ok() { // Success with aplay } else { diff --git a/backend/src/executor.rs b/backend/src/executor.rs index 888e2de7..b0e9c9c7 100644 --- a/backend/src/executor.rs +++ b/backend/src/executor.rs @@ -124,25 +124,6 @@ pub struct ExecutorConstants { pub executor_labels: Vec, } -impl ExecutorConstants { - pub fn new() -> Self { - Self { - executor_types: vec![ - ExecutorConfig::Echo, - ExecutorConfig::Claude, - ExecutorConfig::Amp, - ExecutorConfig::Gemini, - ], - executor_labels: vec![ - "Echo (Test Mode)".to_string(), - "Claude".to_string(), - "Amp".to_string(), - "Gemini".to_string(), - ], - } - } -} - impl ExecutorConfig { pub fn create_executor(&self) -> Box { match self { diff --git a/backend/src/executors/amp.rs b/backend/src/executors/amp.rs index 6ca44362..bfae33c4 100644 --- a/backend/src/executors/amp.rs +++ b/backend/src/executors/amp.rs @@ -67,8 +67,8 @@ impl Executor for AmpExecutor { impl Executor for AmpFollowupExecutor { async fn spawn( &self, - pool: &sqlx::SqlitePool, - task_id: Uuid, + _pool: &sqlx::SqlitePool, + _task_id: Uuid, worktree_path: &str, ) -> Result { use std::process::Stdio; diff --git a/backend/src/executors/claude.rs b/backend/src/executors/claude.rs index ebf1fcde..69339aca 100644 --- a/backend/src/executors/claude.rs +++ b/backend/src/executors/claude.rs @@ -62,8 +62,8 @@ impl Executor for ClaudeExecutor { impl Executor for ClaudeFollowupExecutor { async fn spawn( &self, - pool: &sqlx::SqlitePool, - task_id: Uuid, + _pool: &sqlx::SqlitePool, + _task_id: Uuid, worktree_path: &str, ) -> Result { // Use Claude CLI with --resume flag to continue the session diff --git a/backend/src/executors/gemini.rs b/backend/src/executors/gemini.rs index 7c9708e9..a639ea31 100644 --- a/backend/src/executors/gemini.rs +++ b/backend/src/executors/gemini.rs @@ -60,8 +60,8 @@ impl Executor for GeminiExecutor { impl Executor for GeminiFollowupExecutor { async fn spawn( &self, - pool: &sqlx::SqlitePool, - task_id: Uuid, + _pool: &sqlx::SqlitePool, + _task_id: Uuid, worktree_path: &str, ) -> Result { // Use Gemini CLI with session resumption (if supported) diff --git a/backend/src/main.rs b/backend/src/main.rs index 3cabc9c3..686331e3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -189,7 +189,7 @@ async fn main() -> anyhow::Result<()> { let port: u16 = std::env::var("PORT") .ok() .and_then(|p| p.parse().ok()) - .unwrap_or_else(|| if cfg!(debug_assertions) { 3001 } else { 0 }); + .unwrap_or(if cfg!(debug_assertions) { 3001 } else { 0 }); let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await?; let actual_port = listener.local_addr()?.port(); // get → 53427 (example) diff --git a/backend/src/models/config.rs b/backend/src/models/config.rs index ce88101a..e81bcaa5 100644 --- a/backend/src/models/config.rs +++ b/backend/src/models/config.rs @@ -97,6 +97,12 @@ impl EditorConstants { } } +impl Default for EditorConstants { + fn default() -> Self { + Self::new() + } +} + impl SoundConstants { pub fn new() -> Self { Self { @@ -122,6 +128,12 @@ impl SoundConstants { } } +impl Default for SoundConstants { + fn default() -> Self { + Self::new() + } +} + impl Default for Config { fn default() -> Self { Self { diff --git a/backend/src/models/execution_process.rs b/backend/src/models/execution_process.rs index 95a1a935..1289e70f 100644 --- a/backend/src/models/execution_process.rs +++ b/backend/src/models/execution_process.rs @@ -80,6 +80,7 @@ pub struct CreateExecutionProcess { #[derive(Debug, Deserialize, TS)] #[ts(export)] +#[allow(dead_code)] pub struct UpdateExecutionProcess { pub status: Option, pub exit_code: Option, @@ -392,6 +393,7 @@ impl ExecutionProcess { } /// Delete execution processes for a task attempt (cleanup) + #[allow(dead_code)] pub async fn delete_by_task_attempt_id( pool: &SqlitePool, task_attempt_id: Uuid, diff --git a/backend/src/models/executor_session.rs b/backend/src/models/executor_session.rs index 09db9462..4fb84e8e 100644 --- a/backend/src/models/executor_session.rs +++ b/backend/src/models/executor_session.rs @@ -26,6 +26,7 @@ pub struct CreateExecutorSession { #[derive(Debug, Deserialize, TS)] #[ts(export)] +#[allow(dead_code)] pub struct UpdateExecutorSession { pub session_id: Option, pub prompt: Option, @@ -33,6 +34,7 @@ pub struct UpdateExecutorSession { impl ExecutorSession { /// Find executor session by ID + #[allow(dead_code)] pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as!( ExecutorSession, @@ -76,6 +78,7 @@ impl ExecutorSession { } /// Find all executor sessions for a task attempt + #[allow(dead_code)] pub async fn find_by_task_attempt_id( pool: &SqlitePool, task_attempt_id: Uuid, @@ -154,6 +157,7 @@ impl ExecutorSession { } /// Update executor session prompt + #[allow(dead_code)] pub async fn update_prompt( pool: &SqlitePool, id: Uuid, @@ -173,6 +177,7 @@ impl ExecutorSession { } /// Delete executor sessions for a task attempt (cleanup) + #[allow(dead_code)] pub async fn delete_by_task_attempt_id( pool: &SqlitePool, task_attempt_id: Uuid, diff --git a/backend/src/models/task_attempt.rs b/backend/src/models/task_attempt.rs index 81f6bb81..1bb32635 100644 --- a/backend/src/models/task_attempt.rs +++ b/backend/src/models/task_attempt.rs @@ -620,6 +620,7 @@ impl TaskAttempt { } /// Unified function to start any type of process execution + #[allow(clippy::too_many_arguments)] async fn start_process_execution( pool: &SqlitePool, app_state: &crate::app_state::AppState, @@ -935,7 +936,7 @@ impl TaskAttempt { process_id, crate::app_state::RunningExecution { task_attempt_id: attempt_id, - execution_type, + _execution_type: execution_type, child, }, ) diff --git a/backend/src/models/task_attempt_activity.rs b/backend/src/models/task_attempt_activity.rs index c6ffb3b1..8fefd5c1 100644 --- a/backend/src/models/task_attempt_activity.rs +++ b/backend/src/models/task_attempt_activity.rs @@ -36,6 +36,7 @@ pub struct TaskAttemptActivityWithPrompt { } impl TaskAttemptActivity { + #[allow(dead_code)] pub async fn find_by_execution_process_id( pool: &SqlitePool, execution_process_id: Uuid, @@ -73,6 +74,7 @@ impl TaskAttemptActivity { .await } + #[allow(dead_code)] pub async fn find_processes_with_latest_running_status( pool: &SqlitePool, ) -> Result, sqlx::Error> { diff --git a/backend/src/routes/filesystem.rs b/backend/src/routes/filesystem.rs index ab8dd5e4..49586de4 100644 --- a/backend/src/routes/filesystem.rs +++ b/backend/src/routes/filesystem.rs @@ -58,31 +58,29 @@ pub async fn list_directory( Ok(entries) => { let mut directory_entries = Vec::new(); - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - let metadata = entry.metadata().ok(); + for entry in entries.flatten() { + let path = entry.path(); + let metadata = entry.metadata().ok(); - if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - // Skip hidden files/directories - if name.starts_with('.') && name != ".." { - continue; - } - - let is_directory = metadata.is_some_and(|m| m.is_dir()); - let is_git_repo = if is_directory { - path.join(".git").exists() - } else { - false - }; - - directory_entries.push(DirectoryEntry { - name: name.to_string(), - path: path.to_string_lossy().to_string(), - is_directory, - is_git_repo, - }); + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + // Skip hidden files/directories + if name.starts_with('.') && name != ".." { + continue; } + + let is_directory = metadata.is_some_and(|m| m.is_dir()); + let is_git_repo = if is_directory { + path.join(".git").exists() + } else { + false + }; + + directory_entries.push(DirectoryEntry { + name: name.to_string(), + path: path.to_string_lossy().to_string(), + is_directory, + is_git_repo, + }); } } diff --git a/frontend/package.json b/frontend/package.json index 39e447a0..55e44c90 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,7 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 100", "lint:fix": "eslint . --ext ts,tsx --fix", "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"" @@ -50,4 +50,4 @@ "typescript": "^5.2.2", "vite": "^5.0.8" } -} +} \ No newline at end of file diff --git a/frontend/src/components/projects/project-detail.tsx b/frontend/src/components/projects/project-detail.tsx index 2a97efe7..e0addb87 100644 --- a/frontend/src/components/projects/project-detail.tsx +++ b/frontend/src/components/projects/project-detail.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { @@ -36,7 +36,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { const [showEditForm, setShowEditForm] = useState(false); const [error, setError] = useState(''); - const fetchProject = async () => { + const fetchProject = useCallback(async () => { setLoading(true); setError(''); try { @@ -55,7 +55,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { } finally { setLoading(false); } - }; + }, [projectId]); const handleDelete = async () => { if (!project) return; @@ -86,7 +86,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { useEffect(() => { fetchProject(); - }, [projectId]); + }, [fetchProject]); if (loading) { return ( diff --git a/frontend/src/components/tasks/ExecutionOutputViewer.tsx b/frontend/src/components/tasks/ExecutionOutputViewer.tsx index 97ccad20..96cb9a4b 100644 --- a/frontend/src/components/tasks/ExecutionOutputViewer.tsx +++ b/frontend/src/components/tasks/ExecutionOutputViewer.tsx @@ -42,7 +42,10 @@ export function ExecutionOutputViewer({ // Check if stdout looks like JSONL (for Amp, Claude, or Gemini executor) const { isValidJsonl, jsonlFormat } = useMemo(() => { - if ((!isAmpExecutor && !isClaudeExecutor && !isGeminiExecutor) || !executionProcess.stdout) { + if ( + (!isAmpExecutor && !isClaudeExecutor && !isGeminiExecutor) || + !executionProcess.stdout + ) { return { isValidJsonl: false, jsonlFormat: null }; } @@ -99,7 +102,12 @@ export function ExecutionOutputViewer({ } catch { return { isValidJsonl: false, jsonlFormat: null }; } - }, [isAmpExecutor, isClaudeExecutor, isGeminiExecutor, executionProcess.stdout]); + }, [ + isAmpExecutor, + isClaudeExecutor, + isGeminiExecutor, + executionProcess.stdout, + ]); // Set initial view mode based on JSONL detection useEffect(() => { diff --git a/frontend/src/components/tasks/TaskActivityHistory.tsx b/frontend/src/components/tasks/TaskActivityHistory.tsx index 06fda2e1..07c839b0 100644 --- a/frontend/src/components/tasks/TaskActivityHistory.tsx +++ b/frontend/src/components/tasks/TaskActivityHistory.tsx @@ -86,9 +86,7 @@ export function TaskActivityHistory({ return (
- + {activities.length === 0 ? (
No activities found diff --git a/frontend/src/components/tasks/TaskDetailsHeader.tsx b/frontend/src/components/tasks/TaskDetailsHeader.tsx index c0af96e5..e1bd2c68 100644 --- a/frontend/src/components/tasks/TaskDetailsHeader.tsx +++ b/frontend/src/components/tasks/TaskDetailsHeader.tsx @@ -112,7 +112,7 @@ export function TaskDetailsHeader({

Close panel

- +
diff --git a/frontend/src/components/tasks/TaskDetailsPanel.tsx b/frontend/src/components/tasks/TaskDetailsPanel.tsx index a796f164..07298d84 100644 --- a/frontend/src/components/tasks/TaskDetailsPanel.tsx +++ b/frontend/src/components/tasks/TaskDetailsPanel.tsx @@ -9,11 +9,7 @@ import { getTaskPanelClasses, getBackdropClasses, } from '@/lib/responsive-config'; -import type { - TaskWithAttemptStatus, - EditorType, - Project, -} from 'shared/types'; +import type { TaskWithAttemptStatus, EditorType, Project } from 'shared/types'; interface TaskDetailsPanelProps { task: TaskWithAttemptStatus | null; diff --git a/frontend/src/components/tasks/TaskDetailsToolbar.tsx b/frontend/src/components/tasks/TaskDetailsToolbar.tsx index d88944ea..82d80d17 100644 --- a/frontend/src/components/tasks/TaskDetailsToolbar.tsx +++ b/frontend/src/components/tasks/TaskDetailsToolbar.tsx @@ -105,9 +105,7 @@ export function TaskDetailsToolbar({
) : ( -
- No attempts yet -
+
No attempts yet
)}
@@ -171,9 +169,7 @@ export function TaskDetailsToolbar({

- {isStopping - ? 'Stopping execution...' - : 'Stop execution'} + {isStopping ? 'Stopping execution...' : 'Stop execution'}

@@ -230,8 +226,7 @@ export function TaskDetailsToolbar({ } > {executor.name} - {config?.executor.type === executor.id && - ' (Default)'} + {config?.executor.type === executor.id && ' (Default)'} ))} @@ -257,16 +252,14 @@ export function TaskDetailsToolbar({ onMouseLeave={() => onSetIsHoveringDevServer(false)} >