Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[env]
TS_RS_EXPORT_DIR = { value = "src/types/generated", relative = true }
TS_RS_LARGE_INT = "number"
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ tasks:
cmds:
- cargo test -p adhd-ranch-domain -p adhd-ranch-storage -p adhd-ranch-commands -p adhd-ranch-http-api --all-targets

gen-types:
desc: Regenerate src/types/generated/*.ts from Rust domain via ts-rs
cmds:
- cargo test -p adhd-ranch-domain --features export-ts --quiet

gen-types:check:
desc: Fail if generated TS types are out of sync with Rust domain
cmds:
- task: gen-types
- git diff --exit-code -- src/types/generated
- |
untracked=$(git ls-files --others --exclude-standard -- src/types/generated)
if [ -n "$untracked" ]; then
echo "Untracked generated files (commit them):" >&2
echo "$untracked" >&2
exit 1
fi

Comment thread
coderabbitai[bot] marked this conversation as resolved.
test:web:
desc: Run frontend tests
deps: [install]
Expand Down Expand Up @@ -72,11 +90,12 @@ tasks:
- npm run fmt

check:
desc: PR gate — lint + typecheck + tests
desc: PR gate — lint + typecheck + tests + ts-rs drift check
cmds:
- task: lint
- task: typecheck
- task: test
- task: gen-types:check

build:web:
desc: Build frontend bundle
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"files": {
"ignore": ["dist", "src-tauri", "node_modules"]
"ignore": ["dist", "src-tauri", "node_modules", "src/types/generated"]
},
"organizeImports": {
"enabled": true
Expand Down
4 changes: 4 additions & 0 deletions crates/domain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ path = "src/lib.rs"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
ts-rs = { version = "12", optional = true }
uuid = { version = "1", features = ["v7", "serde"] }

[features]
export-ts = ["dep:ts-rs"]

[dev-dependencies]
6 changes: 6 additions & 0 deletions crates/domain/src/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ impl TaskText {
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct FocusId(pub String);

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Task {
pub id: String,
pub text: String,
Expand All @@ -32,6 +36,8 @@ pub struct Task {
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Focus {
pub id: FocusId,
pub title: String,
Expand Down
2 changes: 2 additions & 0 deletions crates/domain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod caps;
pub mod decision;
pub mod error;
pub mod focus;
pub mod monitor;
pub mod parse;
pub mod pig_rect;
pub mod proposal;
Expand All @@ -15,6 +16,7 @@ pub use caps::{cap_state, CapState};
pub use decision::{Decision, DecisionKind};
pub use error::DomainError;
pub use focus::{Focus, FocusId, Task, TaskText};
pub use monitor::MonitorInfo;
pub use parse::{parse_focus_md, ParseError};
pub use pig_rect::{PigRect, RectUpdater};
pub use proposal::{NewFocus, Proposal, ProposalId, ProposalKind, ProposalValidationError};
Expand Down
9 changes: 9 additions & 0 deletions crates/domain/src/monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct MonitorInfo {
pub idx: usize,
pub label: String,
}
8 changes: 8 additions & 0 deletions crates/domain/src/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ use crate::error::DomainError;
use crate::timer::TimerPreset;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct ProposalId(pub String);

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct NewFocus {
title: String,
description: String,
Expand Down Expand Up @@ -67,6 +71,8 @@ impl<'de> Deserialize<'de> for NewFocus {

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub enum ProposalKind {
AddTask {
target_focus_id: String,
Expand All @@ -79,6 +85,8 @@ pub enum ProposalKind {
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Proposal {
pub id: ProposalId,
#[serde(flatten)]
Expand Down
10 changes: 10 additions & 0 deletions crates/domain/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub const DEFAULT_MAX_FOCUSES: usize = 5;
pub const DEFAULT_MAX_TASKS_PER_FOCUS: usize = 7;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Widget {
pub always_on_top: bool,
pub confirm_delete: bool,
Expand All @@ -19,6 +21,8 @@ impl Default for Widget {
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Caps {
pub max_focuses: usize,
pub max_tasks_per_focus: usize,
Expand All @@ -34,6 +38,8 @@ impl Default for Caps {
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Alerts {
pub system_notifications: bool,
}
Expand All @@ -48,6 +54,8 @@ impl Default for Alerts {

/// Which monitor indices have an active overlay window. Default: primary only (index 0).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct DisplayConfig {
pub enabled_indices: Vec<usize>,
}
Expand All @@ -61,6 +69,8 @@ impl Default for DisplayConfig {
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct Settings {
pub caps: Caps,
pub alerts: Alerts,
Expand Down
6 changes: 6 additions & 0 deletions crates/domain/src/timer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub enum TimerStatus {
Running,
Expired,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub enum TimerPreset {
Two,
Four,
Expand All @@ -30,6 +34,8 @@ impl TimerPreset {
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "export-ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "export-ts", ts(export))]
pub struct FocusTimer {
pub duration_secs: u64,
pub started_at: i64,
Expand Down
2 changes: 1 addition & 1 deletion issues/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Each issue is a self-contained vertical slice an AFK coding agent ("ralph") can

Complete these before picking up any other open issue. All are unblocked and independent — grab any order.

- [ ] [036 — Generate TypeScript types from Rust via ts-rs](036-ts-types-from-rust.md)
_Queue empty — pick from open GitHub issues._

## How to pick up an issue

Expand Down
File renamed without changes.
6 changes: 1 addition & 5 deletions src-tauri/src/ui_bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,7 @@ pub fn update_settings(
Ok(())
}

#[derive(serde::Serialize)]
pub struct MonitorInfo {
pub idx: usize,
pub label: String,
}
use adhd_ranch_domain::MonitorInfo;

#[tauri::command]
pub fn get_monitors(app: AppHandle<Wry>) -> Vec<MonitorInfo> {
Expand Down
10 changes: 8 additions & 2 deletions src/api/fixtureFocusReader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import { createFixtureFocusReader } from "./fixtureFocusReader";
describe("fixtureFocusReader", () => {
it("returns the supplied focuses", async () => {
const focuses: Focus[] = [
{ id: "a", title: "A", description: "", tasks: [] },
{ id: "b", title: "B", description: "", tasks: [{ id: "t1", text: "do", done: false }] },
{ id: "a", title: "A", description: "", created_at: "", tasks: [] },
{
id: "b",
title: "B",
description: "",
created_at: "",
tasks: [{ id: "t1", text: "do", done: false }],
},
];
const reader = createFixtureFocusReader(focuses);
expect(await reader.list()).toEqual(focuses);
Expand Down
1 change: 1 addition & 0 deletions src/api/tauriFocusReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function fromRust(raw: RustFocus): Focus {
id: raw.id,
title: raw.title,
description: raw.description,
created_at: raw.created_at,
tasks: raw.tasks.map((t) => ({ id: t.id, text: t.text, done: t.done ?? false })),
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ vi.mock(import("../hooks/usePigMovement"), async (importOriginal) => {
});

const sample: Focus[] = [
{ id: "a", title: "Customer X bug", description: "", tasks: [] },
{ id: "b", title: "API refactor", description: "", tasks: [] },
{ id: "a", title: "Customer X bug", description: "", created_at: "", tasks: [] },
{ id: "b", title: "API refactor", description: "", created_at: "", tasks: [] },
];

function noopFocusWriter(): FocusWriter {
Expand Down
4 changes: 2 additions & 2 deletions src/components/EditProposalModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { Proposal } from "../types/proposal";
import { EditProposalModal } from "./EditProposalModal";

const focuses: Focus[] = [
{ id: "f1", title: "Customer X bug", description: "", tasks: [] },
{ id: "f2", title: "API refactor", description: "", tasks: [] },
{ id: "f1", title: "Customer X bug", description: "", created_at: "", tasks: [] },
{ id: "f2", title: "API refactor", description: "", created_at: "", tasks: [] },
];

const addTaskProposal: Proposal = {
Expand Down
1 change: 1 addition & 0 deletions src/components/FocusCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const focus: Focus = {
id: "f1",
title: "Customer X bug",
description: "",
created_at: "",
tasks: [{ id: "t1", text: "ship", done: false }],
};

Expand Down
2 changes: 2 additions & 0 deletions src/components/FocusList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const sample: Focus[] = [
id: "a",
title: "Customer X bug",
description: "",
created_at: "",
tasks: [
{ id: "t1", text: "ship the fix", done: false },
{ id: "t2", text: "verify on staging", done: false },
Expand All @@ -17,6 +18,7 @@ const sample: Focus[] = [
id: "b",
title: "API refactor",
description: "",
created_at: "",
tasks: [{ id: "t3", text: "extract pipeline", done: false }],
},
];
Expand Down
4 changes: 3 additions & 1 deletion src/components/PendingTray.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type { Focus } from "../types/focus";
import type { Proposal } from "../types/proposal";
import { PendingTray } from "./PendingTray";

const focuses: Focus[] = [{ id: "f1", title: "Customer X bug", description: "", tasks: [] }];
const focuses: Focus[] = [
{ id: "f1", title: "Customer X bug", description: "", created_at: "", tasks: [] },
];

const addTaskProposal: Proposal = {
id: "p1",
Expand Down
2 changes: 2 additions & 0 deletions src/components/PigDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const baseFocus: Focus = {
id: "pig-a",
title: "Ship it",
description: "",
created_at: "",
tasks: [],
};

Expand Down Expand Up @@ -96,6 +97,7 @@ describe("PigDetail task editing", () => {
id: "pig-a",
title: "Ship it",
description: "",
created_at: "",
tasks: [
{ id: "t1", text: "alpha", done: false },
{ id: "t2", text: "beta", done: true },
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useAppState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe("useAppState", () => {
});

it("becomes ready when both focuses and proposals resolve", async () => {
const focuses: Focus[] = [{ id: "a", title: "A", description: "", tasks: [] }];
const focuses: Focus[] = [{ id: "a", title: "A", description: "", created_at: "", tasks: [] }];
const proposals: Proposal[] = [];
const { result } = renderHook(() =>
useAppState({
Expand Down
Loading
Loading