Skip to content

feat(domain): T-223 settings KV + persist current_project_id (closes #91)#117

Merged
mpiton merged 4 commits into
mainfrom
feat/i-91-t-223-settings-kv-persistence
May 14, 2026
Merged

feat(domain): T-223 settings KV + persist current_project_id (closes #91)#117
mpiton merged 4 commits into
mainfrom
feat/i-91-t-223-settings-kv-persistence

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented May 14, 2026

Summary

  • Add a generic settings KV table (V002 migration) and a new domain/settings/ bounded context with SettingsRepository port + libsql adapter.
  • Expose settings_get / settings_set IPC commands (registered + specta-anchored + ts contract regenerated).
  • Wire React boot to restore ui.selected_project_id after setProjects (so reconcileSelectedId cannot drop a stale id) and persist every selectedId change via a single useProjectStore.subscribe listener mounted at App level.

Closes #91 (T-223, Sprint 2 P1).

Why

Sprint 1 left no settings table — Sprint 2's project-tab persistence (issue spec) needs a backend home for ui.selected_project_id. Generic KV here keeps door open for future UI prefs (kanban column widths, sidebar state, …) without revisiting the schema.

Changes

Backend (Rust + libsql)

  • V002__settings.sql: settings (key TEXT PRIMARY KEY, value_json TEXT NOT NULL, updated_at INTEGER NOT NULL).
  • domain/settings/{mod,model,ports,service}.rs: SettingsRepository trait (get / set), service validating keys (non-empty, ≤256 chars, no control / bidi) and values (no NUL, no bidi, ≤64 KB), SELECTED_PROJECT_ID_KEY constant mirrored to TS.
  • infrastructure/persistence/settings_repo.rs: LibsqlSettingsRepo upsert via INSERT … ON CONFLICT(key) DO UPDATE. Stable map_db_err (ADR-016 — no libsql wording leaks; raw text → tracing::error!).
  • application/di.rs: new settings_repo: Arc<dyn SettingsRepository + Send + Sync> field on AppContainer, wired against the same Arc<Database> as the other repos.
  • interfaces/tauri_commands/settings.rs: thin settings_get(key) / settings_set(key, value) adapters; both registered alphabetically in register.rs + anchored in bin/specta-export.rs (use + anchor_commands + COMMANDS_TS).
  • infrastructure/persistence/pool.rs: new V002 + history tests; existing single-migration assertion relaxed to [1, 2].

Frontend (React 19 + Zustand)

  • src/shared/ipc/settings.ts: invokeSettingsGet / invokeSettingsSet.
  • src/features/projects/persist-selected-project-id.ts: SELECTED_PROJECT_ID_KEY = "ui.selected_project_id" (mirrored against the Rust constant by an equality test); loadPersistedSelectedProjectId(): Promise<number | undefined> (rejects null / non-integer / non-positive); persistSelectedProjectId(id: number) fire-and-forget with console.warn on rejection.
  • App.tsx: boot effect awaits loadPersistedSelectedProjectId() AFTER setProjects (so reconcileSelectedId cannot drop a stale id before validation), restores when the persisted id matches a known project, falls back to first-project otherwise; second useEffect mounts useProjectStore.subscribe so every selectedId change persists without touching ProjectTabBar or useProjectCreateForm.

Acceptance criteria

  • V002 migration applied at boot (refinery picks up the new file via embed_migrations!).
  • Cold-start fallback: persisted id restores when present + valid; first project when missing or stale; none when project list is empty.
  • Integration test against tempdir DB (settings_repo 9 cases + DI parity test).
  • Vitest: switching projects writes the setting (App.test.tsx T-223 describe block, 6 cases).

Test plan

  • cargo test --workspace — 274/274 pass.
  • cargo clippy --workspace --all-targets -- -D warnings — 0 errors.
  • cargo fmt --check clean.
  • cargo deny check — advisories / bans / licenses / sources OK.
  • pnpm exec vitest run — 330/330 pass.
  • pnpm exec tsc -b — clean.
  • pnpm exec oxlint src/ — 0 errors (22 warnings, baseline parity).
  • pnpm exec oxfmt --check src/ — clean.
  • pnpm codegencommands.ts regenerated with the two new entries (drift gate green).
  • pnpm exec knip — no new unused exports.

Notes for reviewers

  • Why the persist subscription lives in App.tsx and not in the store: Zustand store stays infrastructure-free per ARCHI.md hexagonal rule; persistence is a side-effect that belongs at the composition boundary. Single useProjectStore.subscribe listener at App level avoids threading the side-effect through every select(id) call site (3 today: boot, ProjectTabBar tab click, useProjectCreateForm.finalize).
  • Why the restore awaits AFTER setProjects: the store's reconcileSelectedId clears selectedId if it's not in the freshly-loaded project list. Setting the persisted id BEFORE setProjects would race against this and silently drop the restore. The boot order is: setProjects(projects)await loadPersistedSelectedProjectId() → if persisted id matches a known project use it, else first project, else none.
  • Stale persisted id semantics: when the persisted id no longer matches any project (e.g., user deleted the project from another shell), the boot falls back to first project and the new selection persists immediately via the subscribe listener — old key is overwritten, no orphan rows.
  • No delete on SettingsRepository: out-of-scope for Sprint 2 (issue T-223: Persist current_project_id across restarts (settings KV) #91 spec is get + set). Trivial to add when a future feature needs it.
  • value_json column name: the doc string + module comments document that values are opaque strings; callers JSON-encode if they need structure. The current writer (persistSelectedProjectId) stores a stringified i64.

Summary by CodeRabbit

  • New Features

    • Selected project ID now persists across restarts via a new settings store and renderer↔backend commands.
  • Bug Fixes

    • Safer restoration (ignores invalid/unsafe IDs), storage errors are logged (not thrown), and concurrent saves are serialized for last-write-wins.
  • Tests

    • Expanded backend and frontend tests, DB migration coverage, write-order/regression tests, and stabilized time-dependent snapshots.
  • Documentation

    • CHANGELOG updated.

Review Change Stack

closes #91)

Add a generic key-value `settings` table (V002 migration) with a new
`domain/settings/` bounded context (`SettingsRepository` port + libsql
adapter), expose `settings_get` / `settings_set` IPC commands, and wire
the React boot flow to restore the selected project tab across restarts.

Domain `service` validates keys (non-empty, ≤256 chars, no control / bidi)
and values (no NUL, no bidi, ≤64 KB) so libsql sees nothing dangerous.
Adapter folds every libsql failure into a stable
`DomainError::IllegalState("database unavailable")` per ADR-016 and emits
the raw text via `tracing::error!` for Sentry.

Frontend restores the persisted id AFTER `setProjects` so the store's
`reconcileSelectedId` cannot drop a stale id before the existence check
falls back to the first project. A single `useProjectStore.subscribe`
listener mounted at App level persists every `selectedId` change without
touching `ProjectTabBar` or `useProjectCreateForm`.

Tests: 17 service-layer + 9 libsql integration + 2 V002 pool + 2 IPC
anchor + 14 helper vitest + 6 App-level T-223 cases.
Cargo 274/274, vitest 330/330, clippy 0 errors, tsc clean.
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 195b6001-5e1a-42af-9eb6-f5be251a56a0

📥 Commits

Reviewing files that changed from the base of the PR and between 3077b66 and 8cadbed.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/App.tsx
  • src/features/projects/persist-selected-project-id.test.ts
  • src/features/projects/persist-selected-project-id.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/App.tsx
  • src/features/projects/persist-selected-project-id.ts

📝 Walkthrough

Walkthrough

End-to-end persistence for selected project ID: adds V002 settings KV migration, settings domain/service with validation, Libsql adapter, DI wiring, Tauri IPC commands and typed contract, frontend IPC helpers, App boot restoration and persistence-on-change, and backend/frontend tests.

Changes

Settings KV Persistence & Project Selection

Layer / File(s) Summary
Database Schema & Domain Contract
src-tauri/src/infrastructure/persistence/migrations/V002__settings.sql, src-tauri/src/domain/mod.rs, src-tauri/src/domain/settings/mod.rs, src-tauri/src/domain/settings/model.rs, src-tauri/src/domain/settings/ports.rs
V002 migration creates settings(key PRIMARY KEY, value_json, updated_at); SettingsRepository async port defines get/set; SELECTED_PROJECT_ID_KEY constant and stability test.
Domain Service with Input Validation
src-tauri/src/domain/settings/service.rs
get_setting/set_setting validate keys (trim, non-empty, ≤256 chars, no control/bidi) and values (≤64KB bytes, no NUL/bidi), then delegate to repo; comprehensive unit tests.
Libsql Repository Adapter & Tests
src-tauri/src/infrastructure/persistence/settings_repo.rs
LibsqlSettingsRepo implements SettingsRepository with per-call connections, PRAGMA foreign_keys = ON, upsert INSERT ... ON CONFLICT(key) DO UPDATE, stable DomainError mapping, and integration tests validating behavior and schema.
DI Wiring & Migration Tests
src-tauri/src/application/di.rs, src-tauri/src/infrastructure/persistence/mod.rs, src-tauri/src/infrastructure/persistence/pool.rs
AppContainer gains settings_repo wired to LibsqlSettingsRepo; persistence module export added; pool tests updated to assert V002 creation and schema-history/idempotency.
Tauri IPC Commands & Type Generation
src-tauri/src/interfaces/tauri_commands/settings.rs, src-tauri/src/interfaces/tauri_commands/mod.rs, src-tauri/src/interfaces/tauri_commands/register.rs, src-tauri/src/bin/specta-export.rs, src/shared/types/commands.ts
Adds settings_get(key) -> Option<String> and settings_set(key,value) commands, registered and anchored for reachability; specta-export updates Commands contract types.
Frontend IPC Wrappers & Persistence Helpers
src/shared/ipc/settings.ts, src/features/projects/persist-selected-project-id.ts, src/features/projects/persist-selected-project-id.test.ts
invokeSettingsGet/invokeSettingsSet helpers; persistSelectedProjectId serializes writes via module-scoped writeChain and logs failures; loadPersistedSelectedProjectId reads, validates safe integer and min-ID, returns undefined for null/malformed/errors; tests cover serialization, parsing, and error-swallowing.
App Boot & Persistence Integration
src/App.tsx, src/App.test.tsx
On projects load, App attempts to restore persisted selectedId before auto-selecting first project; top-level effect persists selectedId on changes; tests cover restoration, fallback, and persistence writes.
Changelog
CHANGELOG.md
Unreleased T-223 entries updated to document fixed/added behaviors for selected-project persistence and settings KV changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • mpiton/forgent#54: Related to Tauri handler registration changes extended here with settings_get/settings_set.
  • mpiton/forgent#51: Adds the specta-export / commands.ts generation pipeline that this PR extends to include settings commands.
  • mpiton/forgent#115: Modifies App selection logic that this PR augments with persisted-selection restore.

"I'm a rabbit with a tiny key,
I hop and stash what projects be.
A KV table keeps my little cheer,
Restored at boot when you reappear.
Hooray for saved selections — carrot near!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(domain): T-223 settings KV + persist current_project_id (closes #91)' clearly and specifically describes the main change: adding a settings KV system and persisting the selected project ID. It follows conventional commit format, references the tracking ID, and closes the linked issue.
Linked Issues check ✅ Passed All acceptance criteria from issue #91 are met: V002 migration added and tested; SettingsRepository port with libsql implementation; IPC commands registered; frontend loads/persists selected project ID; integration and unit tests included.
Out of Scope Changes check ✅ Passed All changes are scoped to the settings persistence feature requested in T-223. No unrelated refactoring, dependency upgrades, or out-of-scope functionality observed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/i-91-t-223-settings-kv-persistence

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/projects/persist-selected-project-id.test.ts`:
- Around line 1-24: The test creates a spy via vi.spyOn(console, "warn") but
only clears it, not restoring it, which can leak into other tests; add an
afterAll hook that calls consoleWarnSpy.mockRestore() (referencing the
consoleWarnSpy identifier in persist-selected-project-id.test.ts) to fully
restore console.warn after the suite finishes, leaving the existing
beforeEach/afterEach resets intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08ecf3c4-7f3c-447e-a8a9-547299a16fa6

📥 Commits

Reviewing files that changed from the base of the PR and between 8abf863 and d944458.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • src-tauri/src/application/di.rs
  • src-tauri/src/bin/specta-export.rs
  • src-tauri/src/domain/mod.rs
  • src-tauri/src/domain/settings/mod.rs
  • src-tauri/src/domain/settings/model.rs
  • src-tauri/src/domain/settings/ports.rs
  • src-tauri/src/domain/settings/service.rs
  • src-tauri/src/infrastructure/persistence/migrations/V002__settings.sql
  • src-tauri/src/infrastructure/persistence/mod.rs
  • src-tauri/src/infrastructure/persistence/pool.rs
  • src-tauri/src/infrastructure/persistence/settings_repo.rs
  • src-tauri/src/interfaces/tauri_commands/mod.rs
  • src-tauri/src/interfaces/tauri_commands/register.rs
  • src-tauri/src/interfaces/tauri_commands/settings.rs
  • src/App.test.tsx
  • src/App.tsx
  • src/features/projects/persist-selected-project-id.test.ts
  • src/features/projects/persist-selected-project-id.ts
  • src/shared/ipc/settings.ts
  • src/shared/types/commands.ts

Comment thread src/features/projects/persist-selected-project-id.test.ts Outdated
@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 14, 2026

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing feat/i-91-t-223-settings-kv-persistence (8cadbed) with main (8abf863)

Open in CodSpeed

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 21 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/projects/persist-selected-project-id.ts">

<violation number="1" location="src/features/projects/persist-selected-project-id.ts:38">
P2: Use a safe-integer check when restoring the persisted project id; otherwise large numeric strings can be rounded and restore the wrong project.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/features/projects/persist-selected-project-id.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d94445888f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/projects/persist-selected-project-id.ts Outdated
…ore console spy

Address PR #117 bot review (CodeRabbit / cubic / Codex):

- 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 the libsql
  layer races on per-call connections.
- Switch `Number.isInteger` to `Number.isSafeInteger` so a stored id beyond
  2^53 cannot silently round and restore the wrong project.
- Add `afterAll` + `mockRestore` on the `console.warn` spy so it does not
  leak across vitest files in the same worker.
- Expose `resetWriteChainForTesting` test seam so `beforeEach` cases
  isolate the chain across runs.

Tests: 14/14 helper cases pass (incl. new chain-ordering and unsafe-int
regression cases). Full vitest 333/333. tsc clean. oxlint 0 errors.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/features/projects/persist-selected-project-id.ts (1)

62-78: ⚡ Quick win

Split parsing from I/O to satisfy the statement-count gate.

Line 62 currently combines fetch, parsing, validation, and fallback in one function and trips max-statements. Extracting value parsing into a helper keeps behavior identical and clears the lint warning.

♻️ Proposed refactor
+function parsePersistedProjectId(value: string | null): number | undefined {
+  if (value === null) {
+    return undefined;
+  }
+  const parsed = Number(value);
+  if (!Number.isSafeInteger(parsed) || parsed < MIN_VALID_PROJECT_ID) {
+    return undefined;
+  }
+  return parsed;
+}
+
 export async function loadPersistedSelectedProjectId(): Promise<number | undefined> {
   try {
     const value = await invokeSettingsGet({ key: SELECTED_PROJECT_ID_KEY });
-    if (value === null) {
-      return undefined;
-    }
-    const parsed = Number(value);
-    if (!Number.isSafeInteger(parsed) || parsed < MIN_VALID_PROJECT_ID) {
-      return undefined;
-    }
-    return parsed;
+    return parsePersistedProjectId(value);
   } catch (error: unknown) {
     const message = error instanceof Error ? error.message : String(error);
     console.warn(`[Forgent] Failed to load persisted selected project id: ${message}`);
     return undefined;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/projects/persist-selected-project-id.ts` around lines 62 - 78,
Extract the parsing/validation logic out of loadPersistedSelectedProjectId into
a small pure helper (e.g., parsePersistedSelectedProjectId) so the function only
does I/O and the helper only does parsing; keep the same behavior: accept the
value returned from invokeSettingsGet({ key: SELECTED_PROJECT_ID_KEY }), return
undefined if value is null, convert with Number(value), return undefined unless
Number.isSafeInteger(parsed) and parsed >= MIN_VALID_PROJECT_ID, otherwise
return the parsed number; ensure loadPersistedSelectedProjectId wraps only the
invokeSettingsGet call in try/catch and delegates parsing to
parsePersistedSelectedProjectId, preserving the existing error logging using the
same message text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 15: The Unreleased section contains a duplicate heading "### Fixed";
remove the duplication by either merging the content under the existing "###
Fixed" heading or renaming the second heading to a qualifier (e.g., "### Fixed
(internal)") so there is only one canonical "### Fixed" heading in the
Unreleased block; update the heading text where the second "### Fixed" appears
to the chosen name and move any entries under it into the consolidated section
if you merge.

---

Nitpick comments:
In `@src/features/projects/persist-selected-project-id.ts`:
- Around line 62-78: Extract the parsing/validation logic out of
loadPersistedSelectedProjectId into a small pure helper (e.g.,
parsePersistedSelectedProjectId) so the function only does I/O and the helper
only does parsing; keep the same behavior: accept the value returned from
invokeSettingsGet({ key: SELECTED_PROJECT_ID_KEY }), return undefined if value
is null, convert with Number(value), return undefined unless
Number.isSafeInteger(parsed) and parsed >= MIN_VALID_PROJECT_ID, otherwise
return the parsed number; ensure loadPersistedSelectedProjectId wraps only the
invokeSettingsGet call in try/catch and delegates parsing to
parsePersistedSelectedProjectId, preserving the existing error logging using the
same message text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b120d12-7856-4795-9c8f-d57bdf63bc98

📥 Commits

Reviewing files that changed from the base of the PR and between d944458 and 81b0ea5.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/features/projects/persist-selected-project-id.test.ts
  • src/features/projects/persist-selected-project-id.ts

Comment thread CHANGELOG.md Outdated
mpiton added 2 commits May 14, 2026 08:46
PR #117 CodeRabbit flagged MD024 (multiple headings with same content) on
the second `### Fixed` under `[Unreleased]`. Merge the new T-223 fix entry
under the existing `### Fixed` section and keep the T-223 add entry under
the existing `### Added` section so each Keep-a-Changelog category appears
exactly once per version.
CI frontend job runs `pnpm exec oxlint . --max-warnings=0` and was failing
with 39 style warnings introduced by T-223. Local `oxlint .` (no flag) only
prints them, so they slipped through pre-push.

Resolutions (all behavioural-equivalent, no scope creep):

- Convert multi-line `//` comment blocks to `/* ... */` so
  `eslint(capitalized-comments)` no longer fires on continuation lines.
- Extract `parsePersistedId` from `loadPersistedSelectedProjectId` so the
  entrypoint stays under the `max-statements: 10` ceiling.
- Extract `resolveBootSelection` from `App.tsx`'s boot `.then(...)` arrow
  for the same reason.
- Move every magic literal in the test file into named `*_ID` / `*_CALL`
  / `*_DELAY_MS` constants and helper `expectInvokeNthCall` /
  `seedDeferredFirstWriteThenResolved` to eliminate `no-magic-numbers`
  + the residual `max-statements`.
- Reorder imports so `Multiple`-syntax declarations precede `Single`-syntax
  ones (sort-imports).
- Rename `T` generic to `TValue` (id-length minimum 2).
- Tighten the persist `useEffect` arrow body to satisfy
  `arrow-body-style`.

Tests: vitest 333/333, tsc clean, `pnpm exec oxlint . --max-warnings=0`
clean (was 39 warnings).
@mpiton mpiton merged commit 7368c67 into main May 14, 2026
24 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T-223: Persist current_project_id across restarts (settings KV)

1 participant