Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Releases: mpiton/forgent

v0.1.0

14 May 16:10
@github-actions github-actions

Choose a tag to compare

First minor release. Bundles every Sprint 2 deliverable (T-219 → T-224) plus the Sprint 1 follow-ups merged since v0.0.2. See per-task entries below for details. Highlights:

  • Kanban project picker (T-215, T-216, T-217, T-218) — multi-project workspace with ProjectTabBar, persisted DB-backed projects table, drag-and-drop kanban columns.
  • Task creation + edit + delete UX (T-219, T-220) — Radix Dialog wizard (3-step), TaskEditDialog with all status / priority fields, ConfirmDeleteDialog with focus-trap hardening.
  • Auto-select first project on hydration (T-221) — useEffect boot effect picks the first project when none is persisted, with refetch on switch.
  • i18n EN+FR parity test (T-222) — vitest hard-fails any new key added to one locale and not mirrored in the other.
  • Settings KV table + persisted current_project_id (T-223) — new domain/settings/ bounded context, V002 migration, IPC commands settings_get / settings_set, App.tsx boot flow restores the user's last selected project across restarts.
  • Sprint 2 smoke E2E + persistence-after-restart (T-224) — scripts/smoke-sprint-2.sh boots pnpm tauri dev twice and asserts (1) project + task creation through the wizard, (2) drag-to-Code persists to libsql, (3) the same row + DOM selection both restore on cold boot. Shipped alongside the Sprint 1 smoke under a single workflow matrix with per-shard artifacts.
  • Smoke E2E unblocked on GH Actions (PR #119) — tauri_plugin_single_instance gated off under --features pilot to remove the D-Bus session bus dependency that was hanging every CI run; workflow now installs dbus-x11 and wraps the smoke under dbus-run-session -- as belt-and-suspenders.

Fixed

  • src-tauri/src/lib.rs + .github/workflows/smoke-e2e.yml — Sprint 2 smoke E2E (T-224) was hanging on every GH Actions run since the merge of #118: tauri-pilot ping responded but tauri-pilot wait --selector timed out at 60s, snapshot artifact was 0 bytes, and the daily app log file was never created. Boot-stage instrumentation (debug/smoke-webview-blank branch, runs 25864797891 / 25865187570 / 25865551513) localised the hang to inside builder.run(generate_context!()) BEFORE the user setup() closure runs — tauri_plugin_single_instance uses zbus on Linux to register a well-known name on the D-Bus session bus and (a) xvfb does not provide a session bus and (b) even with dbus-run-session the plugin races against the bus daemon's name-release bookkeeping when the second consecutive pnpm tauri dev lifecycle starts. Fix: gate the plugin out under cfg(not(feature = "pilot")) so the smoke build skips the D-Bus dependency entirely (production unchanged: every shipped Tauri bundle keeps single_instance because --features pilot is OFF). Added dbus-x11 to the workflow apt list and wrapped the smoke under dbus-run-session -- as belt-and-suspenders against future plugin additions that rediscover a session-bus dependency. Verified by debug-branch run 25866097897: both pnpm tauri dev smoke (sprint-1) and pnpm tauri dev smoke (sprint-2) jobs green end-to-end.

  • src/App.tsx + src/features/projects/persist-selected-project-id.ts (+ tests) — drive pnpm exec oxlint . --max-warnings=0 (CI gate) to 0 warnings on the T-223 surface. (1) Convert multi-line // comment blocks to /* ... */ so eslint(capitalized-comments) does not trigger on continuation lines. (2) Extract parsePersistedId from loadPersistedSelectedProjectId so the entrypoint stays under the max-statements: 10 ceiling. (3) Extract resolveBootSelection from App.tsx's boot effect for the same reason. (4) Replace every magic literal in the test file with named *_ID / *_CALL / *_DELAY_MS constants (no-magic-numbers). (5) Reorder imports so Multiple-syntax declarations precede Single-syntax ones and members are alphabetical (sort-imports). (6) Rename T generic to TValue (id-length). (7) Tighten the persist subscription useEffect arrow body so it satisfies arrow-body-style. CI frontend job now matches local oxlint . --max-warnings=0.

  • src/features/projects/persist-selected-project-id.ts (+ tests) — address PR #117 bot review for T-223. (1) Serialise concurrent persistSelectedProjectId writes through a module-scoped promise chain so rapid select(a) then select(b) clicks cannot overwrite the persisted id with a stale value when libsql races on per-call connections (codex finding). (2) Switch Number.isIntegerNumber.isSafeInteger in loadPersistedSelectedProjectId so an i64 id beyond Number.MAX_SAFE_INTEGER cannot silently round and restore the wrong project (cubic finding). (3) Add afterAll + mockRestore on the console.warn spy so the mock does not leak across vitest worker files (CodeRabbit finding). Test seam resetWriteChainForTesting exposed for beforeEach isolation. New regression cases: serialises concurrent writes in submission order via the module write chain, keeps subsequent writes flowing when an earlier write rejects, returns undefined for ids beyond Number.MAX_SAFE_INTEGER. 14/14 module tests, 333/333 vitest, 0 oxlint errors.

  • src/features/kanban/components/KanbanColumn.test.tsx + src/features/kanban/components/KanbanBoard.test.tsx + src/features/kanban/components/__snapshots__/KanbanColumn.test.tsx.snap + src/features/kanban/components/__snapshots__/KanbanBoard.test.tsx.snap — stabilize relative-time snapshots in kanban tests. Both test files render TaskCard whose created_at_label computes "X days/min/h ago" against Date.now(). With hardcoded created_at: "2026-05-12T00:00:00Z" in the in-test buildTask / DEMO_PROJECT builders, the snapshot output drifted by one day every 24 hours of wall-clock time — failing the populated-board snapshot and seven col-<phase>-3-tasks snapshots every morning regardless of code changes. Wire vi.useFakeTimers({ shouldAdvanceTime: true }) + vi.setSystemTime(new Date("2026-05-14T00:00:00Z")) into the beforeEach of both files and vi.useRealTimers() into the afterEach. shouldAdvanceTime: true keeps async ticks (await, userEvent.setup() v14, Radix transition timers) responsive while the wall-clock value is pinned. Regenerate the eight affected snapshots once at the canonical "2 days ago" / "in 2 days" delta so they stay deterministic regardless of when CI runs. The fix unblocks the T-222 PR (#90) push which was failing the pnpm exec vitest run pre-push hook from the day-1 drift.

Added

  • T-224 — Sprint 2 smoke E2E (closes #92). New scripts/smoke-sprint-2.sh boots pnpm tauri dev --features pilot twice; lifecycle 1 drives the full UI flow (ProjectTabBar + → create smoke-sprint-2 project pointing at $REPO_ROOT → IPC + sqlite cross-check for the new projects row → Backlog + Add task → TaskCreationWizard title "smoke task" → step-1 → step-2 → step-3 advance → submit → IPC + sqlite cross-check for the new tasks row with phase='Backlog'tauri-pilot drag from [data-testid="task-card-<id>"] to [data-testid="kanban-column-code"] with an IPC tasks_move fallback armed because @dnd-kit PointerSensor (activationConstraint: { distance: 6 } in src/features/kanban/hooks/use-kanban-dnd.ts:28) listens to pointerdown/move/up while tauri-pilot drag dispatches the HTML5 drag* family — the AC is tasks.phase persistence, not the input vector → sqlite3 -readonly probe SELECT phase FROM tasks WHERE id = '<id>' returns 'Code' → DOM snapshot anchored on kanban-task-<id> confirms card under kanban-column-code-list); shutdown_app() then issues kill -TERM -- "-$DEV_PID" with SIGKILL fallback (process-group discipline copied verbatim from scripts/smoke-e2e.sh:87-107) and unlinks the tauri-pilot socket so lifecycle 2 binds cleanly; lifecycle 2 boots a fresh pnpm tauri dev instance, waits for the Kanban board to mount, re-probes the sqlite phase (still 'Code' — persistence AC), and waits for the same kanban-task-<id> card to reappear inside kanban-column-code-list (proves T-223 persisted current_project_id AND T-221's tasks-list refetch hits the right project). Three PNG screenshots (target/smoke-sprint-2-{pre-drag,post-drag,post-restart}.png) attach to the PR description for visual review. Hardening reuse from PR #66 / T-134: identical setsid + negative-PID kill process-group discipline so unrelated target/debug/forgent or node ...vite processes are never reaped (CodeRabbit #3217663820); identical plugin_socket_dir() helper mirroring tauri-plugin-pilot/src/server/unix.rs:80 so the CLI and the plugin never disagree on whether $XDG_RUNTIME_DIR is a private directory (CodeRabbit #3217860886); per-lifecycle freshness gate (LIFECYCLE_LOG_SIZE_BEFORE = $(wc -c <"$LOG_FILE") + tail -c "+N" slice + process-substitution grep -q "Forgent ready") so each boot proves it actually mounted the bootstrap layer (CodeRabbit #3217663815 + cubic-dev-ai #3217772727); same redacted *_DISPLAY paths for every [smoke] ... echo line; same cargo install tauri-pilot-cli --version "=0.5.1" --locked pin (workflow). DB + WAL/SHM/journal sidecars are wiped at script start (rm -f "$DB_PATH" "$DB_PATH-wal" "$DB_PATH-shm" "$DB_PATH-journal") because Sprint 2 asserts on specific rows (projects.name, tasks.title) — Sprint 1's file-existence + mtime gate is not strong enough. IPC poller (poll_ipc_for_id) walks .. | objects so the four wrap shapes Sprint 1 normalises against ([], {"result":...}, {"data":...}, {"ok":...}) are all accepted without per-shape branching. Workflow update (.github/workflows/smoke-e2e.yml): single job restructured into a strategy.matrix.scenario of two entries (sprint-1scripts/smoke-e2e.sh, sprint-2scripts/smoke-sprint-2.sh) with fail-fast: false so a sprint-2 regression cannot mask a sprint-1 regression; apt install list extends to sqlite3 + jq (required by smoke-sprint-2.sh's sqlite pr...
Read more
Assets 8
Loading

v0.0.2

11 May 12:28
@github-actions github-actions
f5e3bcc
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Choose a tag to compare

Sprint 1 — Hexagonal foundations. First feature release after the v0.0.1-bootstrap scaffold tag. Closes the Sprint 1 task list (T-101 → T-135, 35 tasks, GH issues #1#49 closed). Sprint goal met: pnpm tauri dev boots the app with a Sidebar landmark, libsql migration V001 (projects, tasks, runs, phase_events) runs on startup, IPC tasks_list returns [], architecture test enforces forbidden imports in domain/, smoke E2E gate green on main. v0.1 MVP remains the post-Sprint-10 target per PRD §1.4 / ARCHI §22.

Fixed

  • scripts/smoke-e2e.sh + .github/workflows/smoke-e2e.yml — three hardening fixes from CodeRabbit's PR #66 review (T-134). (1) The dev server is now launched under setsid so cleanup uses kill -- -$DEV_PID (process group leader) instead of the previous broad pkill -f "target/debug/forgent" / pkill -f "node .*vite" fallbacks that would have reaped any matching user process outside the repo (CodeRabbit #3217663820); the negative-PID signal targets every descendant (pnpm, the tauri-cli wrapper, cargo run, the forgent binary and vite) without false positives. (2) DB and log assertions are now freshness-gated against SMOKE_START_EPOCH (stat -c %Y "$DB_PATH" >= SMOKE_START_EPOCH) and LOG_SIZE_BEFORE (tail -c "+$((LOG_SIZE_BEFORE + 1))" | grep) — a leftover tasks.db from yesterday's green run, or a stale Forgent ready line in the daily-rolled log file, no longer satisfies the gate when bootstrap silently regresses (CodeRabbit #3217663815). (3) cargo install tauri-pilot-cli in the CI workflow is pinned to --version "=0.5.1" --locked to match tauri-plugin-pilot = "0.5.1" in src-tauri/Cargo.toml; running unpinned would silently break the smoke gate the moment 0.6 lands an incompatible ipc / wait / screenshot signature (CodeRabbit #3217663799). Verified locally on 2026年05月11日: [smoke] PASS — Sprint 1 acceptance gate met, including the new mtime ≥ run start and after run start markers in the assertion log.

  • src/shared/observability/sentry.ts — drop pilot.* permission-denied promise rejections at the Sentry SDK boundary via Sentry.init({ ignoreErrors: [/pilot\./] }) (T-134 follow-up, fixes RUST-2). The tauri-plugin-pilot JS bridge fires pilot.__callback IPCs whose rejection is unhandled while build.rs rewrites capabilities/pilot.json between dev rebuilds, surfacing as UnhandledRejection: pilot.__callback not allowed in Sentry. The plugin is gated behind the pilot cargo feature and never ships in release bundles, so these events have zero production value — filtering them at SDK init keeps the dashboard signal-to-noise high during smoke-E2E iteration without touching the live capture path for any other unhandled rejection. ignoreErrors was chosen over a beforeSend filter because the latter requires a null return to drop (forbidden by repo-wide oxlint unicorn/no-null), whereas ignoreErrors matches on the event title / message via regex and short-circuits before any Sentry transport.

  • src/App.tsx + src/shared/i18n/locales/{en,fr}.json — PR #64 review feedback (CodeRabbit + cubic-dev-ai, both inline). The <main aria-label="Main content"> shipped in T-130 hardcoded an English screen-reader label while every other landmark in the shell (sidebar.landmark, sidebar.navigation, onboarding.landmark) routes through react-i18next — under FR locale the main region therefore announced as "Main content" instead of "Contenu principal", breaking the accessibility contract from CLAUDE.md (i18n is mandatory for end-user UI text). Added common.main_landmark key in en ("Main content") and fr ("Contenu principal"), introduced const translate = useT("common") at the top of App(), and bound aria-label={translate("main_landmark")}. The common namespace was the natural home (shell-level landmark, not feature-scoped) — no new namespace registration needed since common is already in NAMESPACES. Existing tests assert by getByRole("complementary", { name: "Sidebar" }) (sidebar landmark) and don't query the main region by accessible name, so no test changes were needed; the new key inherits coverage from the i18n suite's namespace-resolution check. pnpm exec tsc -b clean. pnpm exec oxlint . 0 warnings / 0 errors. pnpm exec oxfmt clean. pnpm exec vitest run 106/106 still pass.

  • src/features/onboarding/components/WelcomeScreen.tsx + WelcomeScreen.test.tsx — PR #63 review feedback (CodeRabbit Major). next() previously used if (!isLast) setStepIndex((current) => current + STEP_DELTA), where isLast came from the render-snapshot closure. On the penultimate step, two rapid clicks both saw isLast = false, both queued current + 1 updaters, and React composed them to push stepIndex past STEPS.length - 1STEPS[stepIndex] then resolved to undefined and the next render crashed on translate("step." + undefined + ".title"). Fix introduces a module-level LAST_INDEX = STEPS.length - STEP_DELTA constant, switches the guard from the stale isLast closure to a fresh stepIndex < LAST_INDEX read, and clamps the updater via Math.min(LAST_INDEX, current + STEP_DELTA) so even racing batched updates cannot overshoot. Added regression test clamps stepIndex to the final step when Next is clicked rapidly on the penultimate step advancing to step 6 then triple-clicking through the boundary, asserting data-step="success" and aria-valuenow <= aria-valuemax. pnpm exec vitest run src/features/onboarding 18/18 pass. pnpm exec oxlint src/features/onboarding clean. pnpm exec tsc -b clean.

  • src-tauri/src/infrastructure/observability/sentry_init.rs + src/shared/observability/sentry.ts + scripts/no-secrets.sh — PR #60 review feedback (CodeRabbit + cubic-dev-ai). Backend sentry_init::init now eagerly parses SENTRY_DSN via sentry::types::Dsn::from_str and returns None (with an eprintln! diagnostic — tracing is not yet wired) when parsing fails, instead of silently building ClientOptions { dsn: None } and still returning a guard. Previous behaviour masked telemetry misconfiguration: a malformed DSN would compile, return Some(guard), and Sentry would no-op without warning. Frontend initSentry now .trim()s import.meta.env.VITE_SENTRY_DSN so whitespace-only values are treated as unset rather than initialising Sentry with a blank string. scripts/no-secrets.sh filename allowlist tightened from \.example$ (which would have let prod.key.example through) to (^|/)\.env(\.[^/]+)?\.example$, scoping the bypass to the .env.example family only. Added init_returns_none_when_dsn_malformed regression test (5/5 sentry_init tests pass). cargo clippy --lib -- -D warnings clean. pnpm exec tsc -b / oxlint / oxfmt / vitest 61 clean.

Added

  • scripts/smoke-e2e.sh + .github/workflows/smoke-e2e.yml + src-tauri/build.rs + src-tauri/Cargo.toml + src-tauri/src/lib.rs + src-tauri/.gitignore — Sprint 1 acceptance gate (T-134, GH issue #48, ARCHI §21 livrable Sprint 1, depends on T-130 / T-120 / T-121 already on main). The smoke flow boots pnpm tauri dev --features pilot, polls tauri-pilot ping (default 180 s local / 300 s in CI), then asserts the five Sprint 1 acceptance criteria in order: the Sidebar landmark (<aside aria-label> from T-128) is mounted in the WebView via tauri-pilot wait --selector "aside[aria-label]" followed by a snapshot grep that accepts either Main navigation (EN), Navigation principale (FR) or the locale-free data-testid="usage-indicator" footer pill (so the assertion does not break when the WebView falls back to the host locale — i18next-browser-languagedetector returns FR on a French Linux session even with EN-only fallback configured), tauri-pilot ipc tasks_list --args '{}' returns the literal [] (also accepts envelope-wrapped {"result":[]} / {"data":[]} / {"ok":[]} to stay compatible with any future tauri-pilot output-format change; the v0.5 --args flag replaced the v0.4 positional '{}' form referenced in CLAUDE.md / T-134.md), <repo>/.claude/forgent/tasks.db exists on disk (libsql sqlite created by AppContainer::build), ~/.forgent/logs/forgent.log.$(date -u +%Y-%m-%d) contains the Forgent ready line emitted by bootstrap() after app.manage, and tauri-pilot screenshot --output target/smoke-screenshot.png (falls back to the positional form for older pilot builds) produces a non-empty PNG. Cleanup traps EXIT/INT/TERM, gives pnpm tauri dev 5 s SIGTERM grace then SIGKILLs the top-level wrapper plus orphaned target/debug/forgent / node ...vite children (the dev-server double-fork was the root cause of port 1420 staying claimed across reruns) and dumps the last 40 lines of target/smoke-logs/tauri-dev.log to stderr on failure. The script exports FORGENT_PROJECT_ROOT="$REPO_ROOT" before launching the dev command: tauri-cli cds into src-tauri/ before invoking cargo run, so without that env var AppConfig::load's current_dir() call resolves to the cargo manifest dir and the DB lands at src-tauri/.claude/forgent/tasks.db instead of the repo root — failing the acceptance criterion silently. TAURI_PILOT_SOCKET="$XDG_RUNTIME_DIR/tauri-pilot-com.forgent.app.sock" is also exported up-front: tauri-pilot defaults to com.pilot.test-app for the socket name (auto-detection only works when exactly one pilot-enabled binary is on the system), so without the override the CLI talks to a stale socket from another project. Three companion infra pieces make the script actually runnable end-to-end:
    1. Cargo.toml gains an optional tauri-plugin-pilot = "0.5.1" dependency from crates.io (added via cargo add --optional tauri-plugin-pilot) and a corresponding [features] entry pilot = ["dep:tauri-plugin-pilot"] plus the auto-generated alias tauri-plugin-pilot = ["pilot"] (kept for tooling that infers the feature name from the dep name). The flag is off by default — release ...
Read more
Loading

AltStyle によって変換されたページ (->オリジナル) /