refactor(server): replace JSON.parse type assertions with Zod validation#691
Merged
Merged
Conversation
Replace unsafe 'JSON.parse(content) as Type' patterns with proper Zod schema validation. This fixes type-aware lint errors and provides runtime safety. - pid-lock.ts: Add pidLockInfoSchema and parsePidLockInfo helper - package-version.ts: Add packageJsonSchema and parsePackageJson helper - relay-transport.ts: Add isRecord type guard to avoid unsafe type assertion
boudra
added a commit
to yuruiz/paseo
that referenced
this pull request
May 5, 2026
…ion (getpaseo#691) Replace unsafe 'JSON.parse(content) as Type' patterns with proper Zod schema validation. This fixes type-aware lint errors and provides runtime safety. - pid-lock.ts: Add pidLockInfoSchema and parsePidLockInfo helper - package-version.ts: Add packageJsonSchema and parsePackageJson helper - relay-transport.ts: Add isRecord type guard to avoid unsafe type assertion
Avi-Bendetsky
added a commit
to BAS-More/paseo
that referenced
this pull request
May 17, 2026
* fix: update lockfile signatures and Nix hash * Replace fictional fastlane action with Spaceship build-processing poll The Fastfile called wait_for_build_processing_to_be_complete, which isn't a built-in fastlane action and isn't published as a plugin anywhere on rubygems. The submit_review lane has therefore failed on every release that reached it (v0.1.65-beta.1, v0.1.66, v0.1.67). Replace it with a direct Spaceship::ConnectAPI poll: fetch the build matching the latest TestFlight build number, wait until its processing_state is VALID, then hand off to deliver. 30-minute cap with a clear failure message on INVALID/FAILED or timeout. Spaceship ships with fastlane so no plugin or extra gem is required. Lands in the next release; v0.1.67 needs a manual review submission from App Store Connect. * Drop subagent task notifications from parent timeline Subagent task_notification system messages arrive without parent_tool_use_id but with tool_use_id pointing at the parent's Task call, so they slip past the sidechain router and render as top-level "Task Notification" rows in the parent's timeline. Skip them when the referenced tool_use is a Task call; parent-level background bash notifications still flow through. * Remove unnecessary type assertions across codebase Add oxlint-tsgolint and configure typescript/no-unnecessary-type-assertion to flag redundant `!` and `as Foo` casts. Type-aware mode is left off by default to keep `npm run lint` fast; the rule sits configured for when we turn type-aware on intentionally. Auto-fix removed ~283 redundant casts; two manual touch-ups: a real tsgolint false positive in split-container.tsx and a stale ChildProcess import after a double-cast collapsed. * fix: update lockfile signatures and Nix hash * Type CLI command host/json options at the source The CommandOptions interface in packages/cli/src/output/with-output.ts had an open `[key: string]: unknown` indexer, so every consumer of the global --host and --json options had to cast `options.host as string | undefined` at the call site. Add `host?: string` and `json?: boolean` to the interface and remove 29 redundant casts across 12 command files. * Add --mode to schedule and loop CLI, default background runs to unattended mode Plumb --mode/--verify-mode through paseo schedule create and paseo loop run. Mark each provider's unattended mode (claude bypassPermissions, codex/opencode full-access, copilot autopilot) so loop and schedule services pick it by default when no explicit modeId is given. * fix(opencode): forward provider retries instead of swallowing them OpenCode subproviders (e.g. opencode/kimi-k2.6 on OpenCode Zen) emit session.status:retry events with messages like "Internal server error" when the upstream provider returns 5xx. opencode itself retries indefinitely with backoff and never emits a terminal event for these. Previously the adapter only surfaced retry messages on a small allowlist of "fatal" tokens (insufficient balance, invalid api key, etc.) and silently dropped everything else. The agent appeared hung from the user's perspective — no message, no spinner update, nothing — until the upstream eventually recovered or the user manually interrupted. Forward every session.status:retry as a non-terminal timeline error item so the user can see what opencode is doing, mirroring opencode's own TUI. Drop the fatal-token allowlist: classifying which retries are "really" terminal is opencode's job, and synthesizing turn_failed for ones we guess at is misleading anyway because opencode keeps spending upstream while we tell the user the agent is done. Also a small design pass on the timeline error rendering: - drop the redundant "Agent error" prefix (the message is descriptive) - drop the colored box background (visual weight too high for a retry) - align the icon vertically to the first text line (height+center) - make the message text selectable so users can copy errors * Switch voice turn controller to streaming transcription * voice: quieter thinking tone, log cleanup, and small ui polish - Add scripts/lower-thinking-tone.mjs to scale the thinking-tone PCM amplitude in-place; apply it at 0.1 so the cue is a soft indicator instead of triggering the VAD via mic feedback. - Drop the "Received first voice_audio_chunk" log that re-fires every summary window because the chunk counter is reset. - Match Spoke message icon and label sizing to the standard tool-call badge. - Append a spoken-input instruction so voice replies route through the speak tool. * cli(schedule): require --cwd when --host is set (getpaseo#685) process.cwd() is the local path and won't exist on a remote daemon, so fail fast client-side instead of running the schedule in a wrong dir. * fix(cli): update schedule provider/mode error message substring in test The error message gained a `--mode` mention when the schedule mode flag landed; the existing CLI integration test still asserted on the old exact-prefix substring and broke main CI for all PR builds. * feat(cli): paseo worktree create with MCP parity (getpaseo#686) Mirrors the MCP create_worktree tool's three modes (branch-off, checkout-branch, checkout-pr) against the daemon's existing createPaseoWorktree RPC, so CLI users (especially over --host) can provision worktrees in the paseo-managed location. * refactor(relay): remove unnecessary awaits from synchronous crypto functions (getpaseo#687) * mcp(create_agent): validate mode and refuse silent cross-provider inheritance (getpaseo#688) The MCP create_agent tool inherited the parent agent's mode regardless of provider, so a Claude parent in bypassPermissions spawning an opencode child would feed an invalid mode to opencode and the new agent would die in error state. The fix: - Add an optional mode field to the agent-to-agent input schema. - Validate any explicit mode against the target provider's available modes; throw with the list when invalid. - Inherit the caller's mode only when both sides use the same provider; otherwise refuse with a helpful error listing the target's modes. * schedule: fire --every now by default, add run-once for cron-style triggers (getpaseo#689) Interval schedules used to wait the full interval before their first run, which made `--every 1d` feel broken for fresh schedules. Now `--every` fires immediately on creation; `--cron` keeps waiting for the next slot. `--no-run-now` and `--run-now` override the defaults, and the parser rejects redundant combos. Adds `paseo schedule run-once <id>` to manually trigger a single run without advancing cadence or recomputing completion. The new `runOnCreate` field and `schedule/run-once` RPC are additive on the WebSocket schema. * Feat/open projects config to any (getpaseo#681) * feat(server): derive owner/repo display name for any remote host Generalize deriveProjectGroupingName so any remote:<host>/<segments...> project key returns the last two path segments (owner/repo) instead of just the trailing segment. Path-fallback for non-remote keys is unchanged. Brings GitLab, Gitea, Bitbucket, and self-hosted remotes to parity with the prior github.com-only behavior — no separate special-case needed. * feat(app): show projects from any git remote in Projects settings Remove the isSupportedProjectKey filter so workspaces with non-GitHub remotes (GitLab, Gitea, Bitbucket, self-hosted, ssh-style) appear in the Projects list and route to the project settings screen. The daemon RPCs, config schema, and registry were already host-agnostic; this lifts a client-side filter that hid them. Also remove the now-vestigial hiddenUnsupportedRemoteCount field from ProjectSummary, BuildProjectsResult, and UseProjectsResult — once the filter is gone, the count is always zero and the field is dead state. This is an internal app-package type, not a wire schema, so deletion is safe. * chore(app): simplify Projects empty state to "No projects yet" Drop the "Non-GitHub remote projects aren't supported yet" empty-state branch — it can no longer fire now that any git remote is shown. The empty state is unconditional now. * test(app): cover non-GitHub remote in projects-settings e2e Add a fixture that creates a temp git repo with origin pointing at a gitlab.com URL and exercises the same paseo.json read/edit/save flow already covered for local repos. Verifies the project surfaces with the "acme/app" display name and that the round-trip persists correctly. Extracted the remote-setup branch in createTempGitRepo into a small configureRemote helper to keep the main function under the cyclomatic complexity limit. --------- Co-authored-by: Mathias Kurz <mkurz@stamus-networks.com> * refactor(server): replace JSON.parse type assertions with Zod validation (getpaseo#691) Replace unsafe 'JSON.parse(content) as Type' patterns with proper Zod schema validation. This fixes type-aware lint errors and provides runtime safety. - pid-lock.ts: Add pidLockInfoSchema and parsePidLockInfo helper - package-version.ts: Add packageJsonSchema and parsePackageJson helper - relay-transport.ts: Add isRecord type guard to avoid unsafe type assertion * refactor(app): remove redundant type constituents from type definitions (getpaseo#692) * refactor(server): remove redundant null from unknown union types (getpaseo#693) * refactor(server): remove unnecessary String() conversions from type-aware lint fixes (getpaseo#695) * refactor(app): remove unnecessary String() and Boolean() conversions from type-aware lint fixes (getpaseo#696) * fix(server): derive non-GitHub project display names from remote owner/repo (getpaseo#697) Reconciliation overwrote correctly-set project display names with the directory name for non-GitHub remotes (e.g. gitlab.com/acme/app), because buildWorkspaceGitMetadataFromSnapshot only handled GitHub URLs while the registry-model layer used the more general deriveProjectGroupingKey path. Reuse that path here so both layers agree on the owner/repo display name. * schedule: add `paseo schedule update` to edit schedules in place (getpaseo#694) * schedule: add `paseo schedule update` to edit schedules in place Editing a schedule today requires delete+recreate, losing run history and the schedule id. This adds an additive RPC and CLI command that patches name, prompt, cadence, new-agent target fields, max-runs, and expires-in without touching runs or in-flight executions. nextRunAt is recomputed only when the cadence actually changes. * schedule(cli): share cadence flag parser between create and update Both paths were turning --every/--cron into a ScheduleCadence with near-identical code. Extract `parseCadenceFromFlags` so the literals and the exclusivity check live in one place; create wraps it to require a value, update lets it stay optional. * refactor(relay): fix type-aware lint errors in WebSocket and crypto handling (getpaseo#698) * fix(app): make e2e setup an auto fixture so first-of-spec tests get setup (getpaseo#702) * fix(app): make e2e setup an auto fixture so it runs for the first test of every spec The fixtures.ts beforeEach was declared at the top level of a non-test fixture file. Playwright sometimes skipped it for the first test of a subsequent spec when multiple specs ran in the same worker — that test hit page.evaluate without the seed nonce or daemon registry in localStorage and failed (then passed on retry, masking the bug behind retries: 1). Reproduced locally by running sidebar-workspace then startup-loading: the first startup-loading test got no fixture setup and threw "Expected e2e seed nonce". Move the setup into an `auto: true` fixture, the canonical Playwright pattern for running setup for every test in a workspace. The afterEach console-attachment is folded into the same fixture's teardown. * refactor(server): simplify nullish handling in workspace-git-metadata Drop the explicit `null` arg in `deriveProjectSlug(input.cwd, null)` — the second parameter already defaults to `null`. Inline the `repoName && repoName.length > 0` check in `parseGitHubRepoNameFromRemote` since `pop()` on a non-empty array returns a non-empty string here, and `|| null` covers the empty-string case directly. * refactor(speech): add ONNX type augmentation and fix type-aware lint errors (getpaseo#701) * refactor(agent-providers): add toObjectRecord helper and fix type-aware lint errors in codex and claude agents (getpaseo#699) * refactor(server): add getErrorMessage helper and fix error-related type-aware lint errors (getpaseo#704) * refactor(server): add getErrorMessage helper and fix error-related type-aware lint errors in session.ts * fix build: correct parseClientCapabilities type handling * refactor(claude-agent): replace unsafe type assertions with type guards (getpaseo#707) Adds isObjectRecord, isUnknownArray, isChildProcessWithStreams, and isImageMimeType type predicates to eliminate all no-unsafe-type-assertion violations surfaced by typeAware lint mode. Fixes floating promise, adds throw after exhaustive switch, and widens readCompactionMetadata to accept unknown so callers need no casts. * refactor(app): fix type-aware lint errors in voice, tabs store, host-connection, audio recorder (getpaseo#706) Clear all type-aware lint errors in four app state/runtime files: - voice-context.tsx: bind runtime methods before passing to useSyncExternalStore and object spreads to satisfy the unbound-method rule - workspace-tabs-store.ts: replace unsafe `as` casts with isPlainRecord type predicate + toObjectRecord; add coerceWorkspaceTabTarget to bridge unknown storage data to WorkspaceTabTarget without assertions - host-connection.ts: same isPlainRecord/toObjectRecord pattern; replace String(record.x ?? "") calls with typeof guards to fix no-base-to-string - use-audio-recorder.native.ts: construct real Blob instead of casting an object literal; restructure useEffect for consistent-return; drop await on void recorder.record(); use instanceof Error for message extraction; remove redundant Boolean() wrapping * refactor(agent-providers): fix type-aware lint errors in diagnostic-utils and generic-acp-agent (getpaseo#705) * refactor(agent-providers): add type guards and fix type-aware lint errors in diagnostic-utils, generic-acp-agent, and tool-call-detail-primitives * fix(server): defer PTY onExit to allow pending data events to fire On Linux, node-pty's onExit callback can arrive before the last buffered PTY data chunk is delivered via onData. Wrapping finish() in setImmediate gives libuv's I/O poll phase a chance to flush remaining PTY reads before the promise resolves, preventing tail bytes from being silently dropped. Fixes a flaky worktree-bootstrap test that reliably reproduced on Linux CI. * unslop: remove dead guard, fix import order, trim comment - generic-acp-agent: remove isNonEmptyStringArray and its guard; the constructor parameter is already typed [string, ...string[]] so the check can never fire - provider-registry: move isNonEmptyStringArray below the import block instead of between two import groups - worktree: condense 3-line comment to one line per project convention * perf(cli): run CLI E2E tests in parallel (getpaseo#708) * perf(cli): run E2E tests in parallel via worker pool The custom CLI test runner ran 35 tsx test files sequentially, making the cli-tests CI job the longest in main CI (~17 minutes). Each test file already isolates its own daemon (ephemeral port + tmp PASEO_HOME), so parallelism was just gated by the runner. Replace the sequential recursion with a fixed-size worker pool (default concurrency=4 to match GitHub Actions standard runners; override via PASEO_CLI_TEST_CONCURRENCY). Buffer per-test stdout/stderr and flush as a contiguous block on completion so concurrent output stays readable, and report per-test wall clock plus the five slowest tests. Local wall clock drops from ~12-15 minutes serial to ~2:49 with concurrency=4. The slowest single test (05-agent-run, 49s) is now the floor; CI should land near 4-5 minutes. * fix(cli-tests): deflake 30-chat under load and shard CI across 3 runners `chat wait` reads the latest message id and then subscribes for newer messages. Under CI load the subprocess takes >1s to bootstrap, so the old test's 250ms head start before posting "second message" raced against that read. When the post landed first, the subprocess saw the second message as latest and timed out waiting for a newer one. Replace the brittle delay with a post-and-race loop: every iteration posts a fresh "second message" and races a 250ms tick against the wait promise, so whichever message lands after the snapshot wakes wait deterministically. CI was also slower than local (10.3min vs 2.8min). On 4-vCPU GHA runners, 35 sequential-CPU-time of ~1850s caps wall clock around 8 min even at concurrency=4. Shard the suite across 3 GHA runners via matrix strategy. The runner now reads PASEO_CLI_TEST_SHARD/SHARD_TOTAL and distributes files into buckets — known long-pole tests (05/06/11/13/14-...) round-robin first so they spread across shards instead of clustering by their numeric prefixes, then the remainder fills in the reverse direction to balance light load. Local run is unchanged (single shard by default). Expected per-shard wall on CI: ~2:30 + setup ≈ ~4:30 total. * refactor(app): fix type-aware lint errors in UI components (getpaseo#710) * refactor(app): assert store state rather than mock call counts in store tests (getpaseo#715) Rewrites two store tests to verify observable state instead of implementation-level call counts. navigation-active-workspace-store: the "persists" test now uses a functional in-memory AsyncStorage mock that retains written values across vi.resetModules(), then rehydrates a fresh store instance and asserts the resulting selection state matches what was written. checkout-git-actions-store: removes toHaveBeenCalledWith/toHaveBeenCalledTimes assertions on client methods; replaces with getStatus() assertions that reflect the store's actual state after each action completes or fails. * refactor(app): extract pr-pane derivations into pure utils and unit tests (getpaseo#713) Move getStateLabel and getActivityVerb out of pr-pane.tsx into pr-pane-data.ts so they can be tested without JSDOM, React, or vi.mock. Add exhaustive unit tests for both helpers alongside the existing mapPrPaneData/formatAge/deriveAvatarColor coverage. Delete pr-pane.test.tsx — the component test was asserting on derived label/icon strings that are now covered by pure function tests. * test(app): tighten weak assertions in utils tests (getpaseo#714) Replace toBeDefined/toBeTruthy guards with concrete shape assertions using toContainEqual(expect.objectContaining(...)) in diff-highlighter tests, and toMatch(/^script-draft-\d+$/) for the ID check in project-config-form tests. * feat(app/e2e): introduce withWorkspace fixture and DSL helpers (getpaseo#717) Adds a `withWorkspace` Playwright fixture plus composable helpers (permissions, sidebar, composer, agent-stream, settings) so specs read as user-level intent. Migrates workspace-lifecycle and settings-host-page to the new DSL as proof. * refactor(app): make use-agent-input-draft storage injectable (getpaseo#716) Extract DraftStorage interface and createAgentInputDraftCore factory to a new use-agent-input-draft-core.ts. Pure helper functions (resolveDraftKey, resolveEffectiveComposerModelId, etc.) move there too, breaking the test file's transitive dependency on AsyncStorage and useAgentFormState. The test now imports directly from the core module, uses a Map-backed in-memory storage, and has zero vi.mock calls. Tests assert behavior ("save then load returns the same draft") rather than call counts. * refactor(app): extract keyboard shortcut routing into a pure function (getpaseo#718) Replace 8-mock hook test (expo-router, layout, platform, navigation, 4 stores) with a pure unit test of the routing decision. The hook now reads pathname/layout/key, calls routeKeyboardShortcut, and dispatches the resulting ShortcutAction — no behaviour change. * refactor(app): extract resolveAgentForm pure reducer from use-agent-form-state (getpaseo#719) Moves all preference-resolution logic into a pure `resolveAgentForm(state, action)` reducer with a discriminated-union action type. The hook becomes a thin React container: it holds state via `useReducer`, dispatches actions, and owns the hydration-ref timing logic. Removes the `__private__` export and the vi.mock-heavy live test in favour of direct pure-function unit tests (53 tests, zero vi.mock of internal modules). * refactor(app): extract composer-actions module with pure tests (getpaseo#720) Move composer cancel/queue/attachment/dispatch orchestration out of composer.tsx into a React-free actions module. The module takes its dependencies (send client, queue writer, stream writer, attachment persister, image picker) as injected ports, so the new composer-actions.test.ts can drive every action with inline fakes — zero vi.mock of internal modules, zero JSDOM, zero React. The old composer.test.tsx (heavy mocked component test) is removed and replaced with composer-actions.test.ts (31 unit tests). Also extract isWorkspaceAttachment / userAttachmentsOnly / workspaceAttachmentToSubmitAttachment from composer-workspace-attachments.tsx into a sibling .ts so the actions module (and composer-attachments.ts) can use them without dragging React/RN/lucide into a pure test graph. * refactor(app/e2e): migrate workspace-cwd spec to withWorkspace fixture (getpaseo#722) Drops the per-test manual boilerplate (connectWorkspaceSetupClient, createTempGitRepo, seedProjectForWorkspaceSetup, openProject, openHomeWithProject, navigateToWorkspaceViaSidebar, try/finally cleanup) in favour of the withWorkspace fixture landed in getpaseo#717. Both tests — main checkout and worktree — verified green locally. * test(app): triage desktop test files — delete 16-mock component slop, tighten attachment store (getpaseo#721) * test(app): triage desktop test files — delete 16-mock component slop, tighten attachment store assertion Removes desktop-updates-section.test.tsx (16 internal mocks, JSDOM, toHaveBeenCalledWith chains on component renders). Tightens desktop-attachment-store.test.ts: toHaveBeenCalled() → toHaveBeenCalledWith(attachment). * refactor(app): extract daemon management toggle coordinator with unit tests Fills the coverage gap left by deleting desktop-updates-section.test.tsx. Extracts executeDaemonManagementToggle from useDaemonManagementToggle — pure async coordinator with injected deps (no vi.mock). Unit tests verify the three key invariants: settings persist before stop, stop is skipped when not desktop-managed, and start/stop are invoked on enable/disable. * chore(app): clean up e2e helpers — delete duplicates, dead code, and redundant seeding (getpaseo#723) - Delete clickNewTabButton (duplicate of clickNewChat) and clickNewTerminalButton (duplicate of clickNewTerminal); update all call sites - Rename clickTerminal → clickNewTerminal to match clickNewChat naming pattern - Delete waitForLauncherPanel (deprecated no-op) - Delete waitForAgentFinishUI and getToolCallCount (dead exports never imported) - Remove ensureE2EStorageSeeded, assertE2EUsesSeededTestDaemon, and related helpers from app.ts — the paseoE2ESetup auto-fixture seeds via addInitScript on every navigation, making these redundant - Simplify gotoAppShell to a one-liner; simplify gotoHome to use .or() instead of three-way if-else chain - Strip try/catch self-heal from ensureHostSelected — the fixture guarantees preferences alignment, so the workaround is never needed * refactor(app): replace 4 component test slop files with pure module extractions (getpaseo#724) Delete message-input.test.tsx, left-sidebar.test.tsx, agent-stream-view.test.tsx, and agent-panel.test.tsx — all heavy vi.mock + JSDOM + toHaveBeenCalledWith slop. Coverage preserved by extracting the testable derivations: - agent-stream-view-data.ts: isSameAssistantBlockGroup, getAssistantBlockSpacing, resolveInlineWorkingIndicatorItemId — 14 pure unit tests, zero mocks - message-input-state.ts: computeCanStartDictation (dictation readiness gate) — 7 pure unit tests, zero mocks Remaining behaviors confirmed covered by existing E2E: - left-sidebar subscription-while-hidden → sidebar-workspace.spec.ts - agent-panel render isolation + archived agent store hygiene → archive-tab.spec.ts - message-input attachment menu / submit icon → workspace-setup-streaming.spec.ts * refactor(app): replace screen test slop batch 2 with proper coverage (getpaseo#725) Deletes four screen-level .test.tsx files (2600+ lines of vi.mock + JSDOM slop) and replaces each with verified coverage: - sessions-screen.test.tsx: covered by archive-tab.spec.ts which exercises the sessions screen via openSessions() with real agents - workspace-draft-agent-tab.test.tsx: covered by new-workspace.spec.ts "redirects to optimistic draft tab before agent creation resolves" - new-workspace-screen.test.tsx: covered by new-workspace.spec.ts (main submit, branch selection, PR selection, optimistic draft tab) plus new-workspace-picker-item.test.ts (pickerItemToCheckoutRequest) - project-settings-screen.test.tsx: covered by project-config-form.test.ts (all round-trip string/array lifecycle semantics) and projects-settings.spec.ts (save flow with passthrough field preservation) Extracts syncPickerPrAttachment from new-workspace-screen.tsx into a new pure module new-workspace-picker-state.ts with 5 zero-mock unit tests covering: initial PR selection, branch selection without change, PR replacement, PR removal on branch switch, and no-duplicate guard. * refactor(app/e2e): rewrite settings-navigation spec to zero raw locators (getpaseo#726) Replace 21 raw page.locator/getByTestId/getByText calls in the spec body with named DSL operations in helpers/settings.ts. Each test body now reads as prose: open settings, navigate to section, expect content. New helpers: openCompactSettings, expectCompactSettingsList, expectSettingsSidebarVisible/Hidden/Sections, goBackInSettings, expectSettingsBackButton, clickSettingsBackToWorkspace, verifyLegacyHostSettingsRedirect, openCompactSettingsHost, expectHostSettingsUrl, expectAddHostMethodOptions, fillDirectHostUri, expectDirectHostFormValues, expectDirectHostSslEnabled, expectDirectHostUriValue/Hidden, expectDiagnosticsContent, expectAboutContent, expectGeneralContent. Export requireServerId from sidebar.ts so helpers encapsulate serverId logic — spec has no direct env reads. Also fix pre-existing typecheck error in use-keyboard-shortcuts.ts: cast action.route to the expo-router Href type at call sites. * refactor(app/e2e): eliminate raw locators from all offending spec bodies (cluster #13) (getpaseo#727) Rewrites 7 spec files to use DSL helpers throughout — zero raw page.locator/getByText/getByTestId in test() bodies. Adds 30+ new helper primitives across 7 existing helper modules. * test(app/e2e): add desktop-updates spec covering update banner and daemon lifecycle (cluster G4) (getpaseo#733) * test(app/e2e): add desktop-updates spec covering update banner and daemon lifecycle (cluster G4) Adds a new Electron-only E2E spec and companion helper module covering: - Update callout renders with correct version and shows Installing… on click - Daemon management toggle confirm dialog copy, cancel, and confirm flows - Daemon status panel seeded from the real running E2E daemon (version, PID, log path) - Stopping then re-enabling management observes a fresh PID from the stateful IPC mock Exports E2E_PASEO_HOME from globalSetup so tests can read the paseo.pid lock file and derive the daemon log path without hardcoding paths. * fix(app/e2e): address PR review blockers on desktop-updates spec Blocker 1 — ARIA for install button: replace index-based testId locators in clickInstallUpdate and expectInstallInProgress with getByRole("button") using the accessible name ("Install & restart" / "Installing..."). Blocker 2 — Electron dialog path: add dialog.ask to the mock bridge so confirmDialog() hits the Electron code path instead of falling back to window.confirm. The mock stores captured args on window.__capturedDialogCall; interceptDaemonManagementConfirmDialog reads them via waitForFunction+evaluate. Add confirmShouldAccept config flag so tests control accept/dismiss without a Playwright dialog event. Update all daemon management tests to set the flag. Also: console.warn on PID file read failure, comment explaining the no-Electron- runner approach, rename dialog → dialogArgs at call sites. * feat(app/e2e): add PR pane E2E spec with fixture-based seeding (getpaseo#732) * feat(app/e2e): add PR pane E2E spec with fixture-based seeding Adds 7 tests covering open/merged/closed/draft states, check pill counts, activity row count, and the empty-checks graceful render. Fake gh CLI now reads .paseo-e2e-pr.json and .paseo-e2e-timeline.json from the workspace cwd so each test gets isolated fixture data. * refactor(app/e2e): switch PR pane spec to real GitHub fixtures Replace the fixture-file seeding approach with ephemeral real GitHub repos created via the gh CLI. `helpers/github-fixtures.ts` creates a single private repo per test run, pushes one branch per PR scenario, and seeds commit statuses and comments as needed. The fake gh binary now forwards unhandled calls (no fixture file present) to the real gh so the daemon can query live GitHub data. All 7 tests skip gracefully when gh auth is unavailable. * refactor(app/e2e): address PR review feedback on pr-pane spec - helpers/pr-pane: extract assertCheckPill helper so expectPrPaneCheckSummary is a flat 3-call sequence; remove branching on rendering shape - helpers/pr-pane: drop .first() on explorer button and redundant toBeVisible before click; import getStateLabel from @/utils/pr-pane-data instead of duplicating the map - pr-pane.spec.ts: move test.skip and test.setTimeout into beforeEach; replace positional IDX_* constants with workspaceByTitle Map keyed by PR title - helpers/github-fixtures: add IssueSpec/GhIssueFixture and issues[] option; extract seedPr/seedIssue to satisfy complexity limit; make prs/issues optional * test(app/e2e): add composer-attachments spec (8 behaviors) (getpaseo#734) * test(app/e2e): add composer-attachments spec covering 8 attachment behaviors Restores E2E coverage for composer attachment behaviors dropped in PR getpaseo#720: plus-menu visibility, GitHub combobox lazy search, image lightbox, pill render, pill removal (with hover-reveal), queue-on-running-agent, review-pill suppression (test.fixme pending store seeding bridge), and Escape interrupt draft preservation. Includes accessibilityRole="button" on QueuedMessageRow Pressables (was rendering as generic, breaking role-based selectors) and an opt-in E2E debug surface on useWorkspaceAttachmentsStore (localStorage gated, needed for the fixme test). * refactor(app/e2e): unslop composer-attachments spec and helpers Remove dead `expectComposerLocked` export (never imported), trim verbose JSDoc on `pressInterruptShortcut`, and correct the test.fixme comment which said the store wasn't window-exposed (it is, as of the parent commit). * fix(app/e2e): address PR getpaseo#734 review blockers for composer-attachments spec - Remove window.__paseoWorkspaceAttachmentsStore exposure (hard ban on internal state injection) - Merge helpers/composer-attachments.ts into helpers/composer.ts; delete old file - Add openGithubWorkspace, selectGithubOption, expectGithubAttachmentPill, expectComposerDisabled, expectAttachButtonDisabled helpers to composer.ts - Extract delayBrowserAgentCreatedStatus from new-workspace.spec.ts into helpers/new-workspace.ts so it can be shared - Add real GH issue/PR pill tests using createTempGithubRepo fixtures - Rewrite lock-state test to assert textarea disabled + attach button disabled during in-flight workspace creation (submitBehavior=preserve-and-lock) - Add test.fixme with detailed explanation for workspace-review pill (requires diff pane automation not yet in E2E harness) - Add test.fixme for browser-element pill (Electron-only, not testable in headless Chromium) * refactor(app/e2e): unslop composer helpers and spec after blockers fix - Remove AI section headers from composer.ts (not in project convention) - Fix fillComposerDraft: drop redundant click() before fill() - Fix selectGithubOption: extract locator to local var instead of double getByTestId() - Use clickNewWorkspaceButton in lock-state test instead of raw Create button locator - Drop obvious PNG constant comment * test(app/e2e): cover project-settings error-UX paths (Cluster G3) (getpaseo#731) * test(app/e2e): cover 5 error-UX paths + host indicator + script removal for project settings Add helpers/project-settings.ts DSL helpers and extend projects-settings.spec.ts with 5 new tests covering the error paths dropped by PR getpaseo#725: - stale_project_config callout + disabled save + reload recovery - invalid_project_config read callout + reload after fix - write_failed callout + retry + reload recovery - single-host static indicator vs picker chip - script removal via kebab menu + confirm dialog * refactor(app/e2e): unslop project-settings helpers and spec - Fix removeProjectScript: derive trigger testID from row testID instead of using scoped locator (which was timing out) - Extract inline writeFile call in invalid-config test to restorePaseoConfig helper - Replace raw testID click in write_failed test with clickReloadProjectSettings - Remove writeFile from spec imports; drop defensive ?? "" fallback * test(app/e2e): add read-transport and offline no-target tests for project settings - Add read-transport failure test: WS-level drop during readProjectConfig triggers read-transport-callout; Reload retries until WS reconnects and refetch succeeds. - Add no-target test: WS drop after form load triggers NoEditableTarget via live connectionStatus check (useHostRuntimeSnapshot) on the selected host. - Hoist openProjects/editWorktreeSetup from spec body into project-settings helper. - Fix expectNoProjectSettingsError to accept optional timeout (needed for toPass loop). - Add isHostGone to renderContent: after readQuery errors are checked, offline/error connectionStatus renders NoEditableTarget without unmounting ProjectSettingsBody. * fixup(app/e2e): correct misleading comments on WS close → error-state mapping * fix(app): add aria-checked to Switch for E2E toBeChecked() assertions React Native Web does not map accessibilityState.checked to aria-checked for role="switch", so Playwright's toBeChecked() always finds the element unchecked. Adding aria-checked={value} directly to the Pressable sets the attribute explicitly. Update the switch.test.tsx mock to accept and pass through the explicit aria-checked prop, keeping the mock faithful to the fixed component. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> Co-authored-by: Mathias Kurz <46938675+krumpyzoid@users.noreply.github.com> Co-authored-by: Mathias Kurz <mkurz@stamus-networks.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cluster
Root cause
JSON.parse() returns 'any', and the codebase was using type assertions (as Type) instead of proper validation. This is unsafe because invalid JSON would silently pass through with wrong types. The type-aware linter correctly flagged these as errors.
Why this is correct beyond satisfying the linter
Test plan