Latest Results
feat(ci): T-224 Sprint 2 smoke E2E with persistence-after-restart (#118)
* feat(ci): T-224 Sprint 2 smoke E2E + persistence-after-restart (closes #92)
Add scripts/smoke-sprint-2.sh exercising the full Sprint 2 flow:
boot pnpm tauri dev --features pilot → create project via ProjectTabBar
→ open TaskCreationWizard → drag card from Backlog to Code (with
tasks_move IPC fallback for the @dnd-kit PointerSensor / tauri-pilot
drag event-family mismatch) → kill the dev server via process-group
TERM/KILL → reboot → assert tasks.phase still 'Code' AND the card
re-renders under [data-testid="kanban-column-code-list"].
Workflow .github/workflows/smoke-e2e.yml restructures the single job
into a strategy.matrix.scenario (sprint-1, sprint-2) with fail-fast
disabled; apt install extends to sqlite3 + jq (required by the new
script's sqlite probe + IPC poller); artifact paths and names are
scenario-scoped so each shard uploads only its own bundle.
Hardening reuse from PR #66 / T-134 is verbatim: setsid + negative-
PID kill, plugin_socket_dir() XDG_RUNTIME_DIR fallback, per-lifecycle
LOG_SIZE_BEFORE freshness gate, process-substitution log tail, pinned
tauri-pilot-cli=0.5.1 --locked, redacted *_DISPLAY paths.
Sprint 1 collateral fix: scripts/smoke-e2e.sh#L191's tasks_list
--args '{}' is stale since T-211 added a required project_id: i64;
payload changed to '{"project_id":1}' (Sprint 1 has zero projects so
the WHERE filter still returns []). Without this, the new sprint-1
matrix row would block the AC "workflow green on main".
Adversarial review hardening: poll_ipc_for_phase replaces the
DOM-stability gate before sqlite probes (the optimistic-update path
mutates the DOM before invokeTasksMove resolves); CI=true /
FORGENT_SMOKE_FORCE_WIPE=1 guard prevents local invocations from
wiping the developer's real tasks.db; preflight aborts when another
dev server holds the pilot socket; process-group existence checks
use kill -0 -- "-PGID" so cleanup catches orphaned grandchildren;
TASK_ID validated against the v4 UUID regex before SQL interpolation.
CHANGELOG entry under [Unreleased] Added. README smoke section
points at both smoke-e2e.sh and smoke-sprint-2.sh.
* fix(frontend): T-224 follow-up — IPC camelCase + tauri-pilot 0.5.2 + verified smoke green
Round trip from running `FORGENT_SMOKE_FORCE_WIPE=1 ./scripts/smoke-sprint-2.sh`
locally. The new gate caught three production defects + one upstream
tooling bug that vitest could not surface (mocked `invoke` boundary
hides Tauri 2's camelCase rename, and `pnpm tauri dev` is not part of
the unit-test loop). Smoke now PASSES end-to-end on this commit.
Production fixes:
- `src/shared/ipc/tasks.ts` — `invokeTasksList`, `invokeTasksCreate`,
`invokeTasksMove` now send the canonical camelCase wire keys
(`projectId`, `targetPhase`) that Tauri 2's default
`rename_all = "camelCase"` rewrite of `#[tauri::command]` parameter
names expects. The previous snake_case payloads failed silently
with `command X missing required key projectId` / `targetPhase` on
every real IPC call. Invisible to `vitest` because the test suite
mocked `@tauri-apps/api/core`'s `invoke` AND asserted the
(broken) snake_case payload verbatim. `src/shared/ipc/tasks.test.ts`
assertions + test names + a `/* … */` comment block (oxlint
`capitalized-comments` on multi-line `//`) updated; 17/17 cases
pass.
- `scripts/smoke-e2e.sh` — Sprint 1 `tauri-pilot ipc tasks_list`
payload follows the same camelCase rename. Still returns `[]`
unchanged (Sprint 1 has zero projects in the wiped DB so the
`WHERE project_id = ?` filter resolves empty regardless of the
passed id).
Tauri-pilot upstream bump (mpiton/tauri-pilot#91):
- `src-tauri/Cargo.toml` + `Cargo.lock`: `tauri-plugin-pilot` 0.5.1
→ 0.5.2 via `cargo add tauri-plugin-pilot@0.5.2 --optional`
(per CLAUDE.md `cargo add` rule, no manual Cargo.toml edits).
- `.github/workflows/smoke-e2e.yml` + `README.md`:
`cargo install tauri-pilot-cli --version "=0.5.2" --locked`.
- 0.5.2 fixes a long-standing internal-timeout cap on `wait` where
any `--timeout` above 10000 ms was silently truncated to the
hardcoded `DEFAULT_TIMEOUT`. We surfaced the bug from the
Forgent harness — it manifested as "UUID-bearing testids time
out" because the UUID-bearing `<li>` rows hydrate after API
state lands, i.e. after the 10 s cap. Reported in
https://github.com/mpiton/tauri-pilot/issues/91 with the
comparative selector table, fixed in 0.5.2 by the upstream
author within hours.
`scripts/smoke-sprint-2.sh` refinements after live runs:
- `boot_app`'s readiness gate switched from
`[data-testid="kanban-board"]` to `[data-testid="project-tab-bar"]`.
`<KanbanBoard>` only mounts when `selectedProjectId !== undefined`
(`App.tsx:177-186`); a fresh DB has zero projects so lifecycle 1
renders `<TasksEmptyState />` and the old gate would always time
out. `<ProjectTabBar>` is always rendered on the default
`route="tasks"`.
- DOM assertions replaced bounded `tauri-pilot wait --selector`
with `for` loops over `tauri-pilot snapshot` + `grep`. `wait` is
unreliable when the target sits behind a modal Radix Dialog (the
TaskCreationWizard lingers a few frames after `onOpenChange(false)`
and pushes `aria-hidden="true"` onto the background, hiding it
from the matcher); the accessibility-tree snapshot exposes the
full DOM regardless of `aria-hidden`.
- Lifecycle-1 Code-column DOM check removed. Issue #92 step 6 only
requires the sqlite probe (`tasks.phase = 'Code'`); step 7
reserves the DOM check for after restart. The IPC fallback
(`tauri-pilot ipc tasks_move`) writes the DB but bypasses the
Zustand store, so the lifecycle-1 UI does not re-render until a
refetch trigger fires. Lifecycle 2's cold boot triggers a fresh
`tasks_list` fetch via T-221, which is where the DOM assertion
actually belongs per the issue spec.
- `fail()` now dumps a best-effort `tauri-pilot snapshot` to
`target/smoke-sprint-2-logs/snapshot-on-fail-<N>.txt` for
post-mortem when the pilot socket is still up.
Verified locally on commit e8ce3c8's branch:
`FORGENT_SMOKE_FORCE_WIPE=1 ./scripts/smoke-sprint-2.sh` exits 0,
produces all three screenshots, confirms `tasks.phase = 'Code'`
post-lifecycle-1 drag, kills + restarts the app, re-verifies DB
+ DOM persistence after T-223/T-221 boot effects restore the
selection.
* fix(frontend): T-224 follow-up — App.test.tsx tasks_list assertions to camelCase
Two regressions surfaced by the full vitest run on top of commit 1dbd149:
- `App — kanban hydration … renders KanbanBoard and invokes tasks_list`
- `App — kanban hydration … refetches tasks_list … when switching projects`
Both asserted the previously-broken snake_case payload (`{ project_id: 1 }`)
that `invokeTasksList` used to send. After the camelCase fix in
`src/shared/ipc/tasks.ts`, the wrappers send `{ projectId: 1 }` so the
assertions had to follow. 333/333 vitest cases now pass.
CHANGELOG entry under T-224 extended with point (f).
* fix(ci): T-224 PR #118 review pass — readme/header copy + lifecycle-aware log
Three minor follow-ups surfaced by the CodeRabbit + cubic-dev-ai pass on PR #118.
- `README.md` (CodeRabbit #3240080125): Sprint 1 quickstart probe
example `tauri-pilot ipc tasks_list --args '{}'` is stale since
T-211 added a required `project_id: i64` argument. Tauri 2's
default `rename_all = "camelCase"` rewrites the wire key to
`projectId`, so the payload becomes `'{"projectId":1}'`. Sprint 1
has zero projects in the wiped DB, so the `WHERE project_id = ?`
filter still returns `[]`.
- `scripts/smoke-e2e.sh:9` (CodeRabbit #3240080129 + cubic-dev-ai
#3240111500): header comment showed the old positional-JSON form
`tauri_pilot ipc tasks_list '{"projectId":1}'` (underscore +
no `--args`). Updated to match the actual 0.5.x invocation:
`tauri-pilot ipc tasks_list --args '{"projectId":1}'`. No
behavior change — the body of the script already used the correct
form at the call site.
- `scripts/smoke-sprint-2.sh:48` (CodeRabbit #3240080133):
`LOG_FILE="…/forgent.log.$(date -u +%Y-%m-%d)"` was evaluated once
at script-load time, so a lifecycle-2 boot that crosses UTC
midnight would stat yesterday's stale path. Replaced with a
`current_log_file()` helper invoked inside `boot_app` (size-before
capture) and inside the freshness assertion (post-boot). The
user-facing `LOG_FILE_DISPLAY` placeholder changed from a baked-in
date to `<YYYY-MM-DD>` to honestly reflect the lazy resolution.
CHANGELOG entry (g) + (h) appended.
* fix(ci): T-224 PR #118 review pass — UTC roll race between log size capture and grep
cubic-dev-ai PR comment 3241391324 on `scripts/smoke-sprint-2.sh:304`
flagged a residual race after the lazy log-path fix in 00e83a2:
- `boot_app` captures `wc -c < $(current_log_file)` at start.
- The dev server boots, the handshake completes (~tens of seconds
on a cold cargo cache).
- UTC midnight may roll between those two points. The daily roller
inside `tracing_appender` opens a new dated file and writes
`Forgent ready` to it.
- Post-handshake `current_log_file()` resolves to the NEW path,
which the byte offset captured at start refers to a DIFFERENT
file. `tail -c "+SIZE+1"` then either reads past the end of a
smaller new file (empty output) or skips the `Forgent ready`
line altogether.
Fix: capture both the path AND the size at start
(`LIFECYCLE_LOG_PATH_AT_START` + `LIFECYCLE_LOG_SIZE_BEFORE`).
Post-handshake, compare `current_log_file()` against the captured
path. If they match (the common case), slice from the recorded
offset. If they diverged (UTC roll happened during boot), read the
new file from byte 0 — its entire content is post-launch by
definition since the roller created it after the script started.
CHANGELOG entry (i) appended under T-224.
---------
Co-authored-by: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Latest Branches
0%
feat/T-226-shell-detector 0%
fix/smoke-dbus-single-instance 0%
feat/i-92-t-224-smoke-sprint-2-e2e © 2026 CodSpeed Technology