switched from codex to claude as tutor and moved tests to their own folder
This commit is contained in:
26
AGENTS.md
26
AGENTS.md
@@ -1,13 +1,28 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Overview
|
||||
**Owlerlay** is a Tauri-based **OBS overlay control center**. It is not a simple countdown timer — the countdown feature is just one of several planned core features.
|
||||
|
||||
**Goals:**
|
||||
- Act as a control center for OBS browser-source overlays (rendered via HTMX + CSS, minimal JS)
|
||||
- Low resource consumption, cross-platform support
|
||||
- Extensible via a plugin system
|
||||
|
||||
**Planned core features:** alarms, twitch chat integration, countdowns (timers), alerts, and more.
|
||||
|
||||
**Overlay rendering approach:** HTMX + CSS templates served to OBS browser sources; JavaScript kept to an absolute minimum.
|
||||
|
||||
---
|
||||
|
||||
## Learning-First Collaboration Rules
|
||||
This is a learning project. The assistant is a guide, not an implementer.
|
||||
|
||||
- The developer writes all application code and tests.
|
||||
- The assistant must not write or modify project source code (for example `src/` or `src-tauri/src/`).
|
||||
- The developer writes all application code.
|
||||
- The assistant **may** write test files (Rust integration tests, Vitest specs, etc.) when asked.
|
||||
- The assistant must not write or modify project source code (for example `src/` or `src-tauri/src/`), **except** for test files.
|
||||
- The assistant may provide step-by-step guidance, debugging help, reviews, architecture feedback, and hints.
|
||||
- The assistant may edit documentation files when explicitly requested.
|
||||
- If asked to implement code, the assistant should refuse and provide a clear plan the developer can execute.
|
||||
- If asked to implement non-test code, the assistant should refuse and provide a clear plan the developer can execute.
|
||||
- If asked about a file, function, module, or crate, the assistant must read the current code first before answering.
|
||||
- Reviews and guidance must be based strictly on the current file contents, not earlier snapshots.
|
||||
- Before each technical answer, the assistant must re-open the relevant files/outputs and verify against current on-disk state, never relying on memory from earlier turns.
|
||||
@@ -44,6 +59,11 @@ No automated JS test framework is currently configured. For new features:
|
||||
- add frontend tests only when introducing non-trivial UI/state behavior (Vitest is the preferred choice if added).
|
||||
- include manual verification steps in PRs (OS, command run, expected behavior).
|
||||
|
||||
The assistant **is allowed to write test files** when asked. Tests must live in dedicated files, **separate from source code**:
|
||||
- Rust tests: `src-tauri/tests/` (integration tests using the public API of each module)
|
||||
- Frontend tests: `src/tests/` or a top-level `tests/` directory (Vitest specs)
|
||||
- Do **not** add `#[cfg(test)]` blocks inside `src-tauri/src/` files — keep source and test code in separate files.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Git history is not available in this workspace snapshot, so use Conventional Commit style:
|
||||
|
||||
|
||||
63
CLAUDE.md
Normal file
63
CLAUDE.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# CLAUDE.md
|
||||
|
||||
> This file configures Claude Code's behaviour in this repository.
|
||||
> The canonical collaboration rules live in [AGENTS.md](./AGENTS.md).
|
||||
> Claude must read and comply with AGENTS.md on every session.
|
||||
|
||||
---
|
||||
|
||||
## Role
|
||||
|
||||
This is a **learning project**. Claude is a guide and reviewer, not an implementer.
|
||||
|
||||
- **Do not** write or modify files inside `src/` or `src-tauri/src/`.
|
||||
- **Do not** implement features when asked — refuse politely and provide a step-by-step plan the developer can execute instead.
|
||||
- **Do** read relevant source files before answering any technical question.
|
||||
- **Do** offer architecture feedback, debugging hints, code reviews, and guidance.
|
||||
- **Do** edit documentation files when explicitly asked.
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Owlerlay** is a desktop countdown-timer app built with:
|
||||
|
||||
| Layer | Tech |
|
||||
|-------|------|
|
||||
| Frontend | Svelte 5, TypeScript (strict), Vite 7, PicoCSS |
|
||||
| Backend | Rust (Edition 2024), Tauri 2 |
|
||||
| Package manager | pnpm |
|
||||
|
||||
### Key paths
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `src/` | Svelte/TS frontend |
|
||||
| `src-tauri/src/` | Rust Tauri commands & state |
|
||||
| `src-tauri/src/lib.rs` | Tauri builder & command registration |
|
||||
| `src-tauri/src/countdown/` | Countdown feature (commands, logic) |
|
||||
| `src-tauri/src/app_state.rs` | Shared app state |
|
||||
| `src-tauri/tauri.conf.json` | Tauri configuration |
|
||||
|
||||
---
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
pnpm install # install JS deps
|
||||
pnpm dev # Vite dev server (http://localhost:1420)
|
||||
pnpm tauri dev # full app in dev mode
|
||||
pnpm build # tsc + Vite build
|
||||
pnpm tauri build # desktop bundle
|
||||
pnpm check # svelte-check + tsc
|
||||
cargo test --manifest-path src-tauri/Cargo.toml # Rust tests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Coding Conventions (summary — see AGENTS.md for full details)
|
||||
|
||||
- **TypeScript**: 2-space indent, strict mode, `camelCase` vars/fns, `PascalCase` types.
|
||||
- **Rust**: 4-space indent (`rustfmt`), `snake_case` fns/modules, Edition 2024.
|
||||
- Tauri commands: keep small and side-effect focused; register in `lib.rs`.
|
||||
- Commit style: Conventional Commits (`feat:`, `fix:`, etc.).
|
||||
@@ -159,78 +159,3 @@ impl Countdown {
|
||||
self.target_timestamp = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_from_idle_enters_running() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(10));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
assert_eq!(countdown.state(), CountdownState::Running);
|
||||
assert_eq!(countdown.remaining_at(now), Duration::from_secs(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pause_and_resume_preserve_remaining_time() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(10));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
let after_three_seconds = now.checked_add(Duration::from_secs(3)).unwrap();
|
||||
countdown
|
||||
.pause(after_three_seconds)
|
||||
.expect("pause should succeed");
|
||||
assert_eq!(countdown.state(), CountdownState::Paused);
|
||||
assert_eq!(
|
||||
countdown.remaining_at(after_three_seconds),
|
||||
Duration::from_secs(7)
|
||||
);
|
||||
|
||||
let resume_time = after_three_seconds
|
||||
.checked_add(Duration::from_secs(1))
|
||||
.unwrap();
|
||||
countdown
|
||||
.resume(resume_time)
|
||||
.expect("resume should succeed");
|
||||
assert_eq!(countdown.state(), CountdownState::Running);
|
||||
assert_eq!(countdown.remaining_at(resume_time), Duration::from_secs(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_transition_returns_error() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(5));
|
||||
|
||||
let err = countdown
|
||||
.pause(now)
|
||||
.expect_err("pause should fail from idle");
|
||||
assert_eq!(
|
||||
err,
|
||||
CountdownError::InvalidTransition {
|
||||
from: CountdownState::Idle,
|
||||
action: "pause",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_countdown_reaches_finished() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(2));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
let after_two_seconds = now.checked_add(Duration::from_secs(2)).unwrap();
|
||||
countdown.sync_finished_at(after_two_seconds);
|
||||
|
||||
assert_eq!(countdown.state(), CountdownState::Finished);
|
||||
assert_eq!(
|
||||
countdown.remaining_at(after_two_seconds),
|
||||
Duration::from_secs(0)
|
||||
);
|
||||
assert!(countdown.is_finished());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extern crate core;
|
||||
|
||||
mod app_state;
|
||||
mod countdown;
|
||||
pub mod countdown;
|
||||
|
||||
use crate::countdown::commands::{
|
||||
countdown_create, countdown_delete, countdown_list, countdown_pause, countdown_reset,
|
||||
|
||||
73
src-tauri/tests/countdown_model.rs
Normal file
73
src-tauri/tests/countdown_model.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use owlerlay_lib::countdown::errors::CountdownError;
|
||||
use owlerlay_lib::countdown::model::{Countdown, CountdownState};
|
||||
use tokio::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn start_from_idle_enters_running() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(10));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
assert_eq!(countdown.state(), CountdownState::Running);
|
||||
assert_eq!(countdown.remaining_at(now), Duration::from_secs(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pause_and_resume_preserve_remaining_time() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(10));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
let after_three_seconds = now.checked_add(Duration::from_secs(3)).unwrap();
|
||||
countdown
|
||||
.pause(after_three_seconds)
|
||||
.expect("pause should succeed");
|
||||
assert_eq!(countdown.state(), CountdownState::Paused);
|
||||
assert_eq!(
|
||||
countdown.remaining_at(after_three_seconds),
|
||||
Duration::from_secs(7)
|
||||
);
|
||||
|
||||
let resume_time = after_three_seconds
|
||||
.checked_add(Duration::from_secs(1))
|
||||
.unwrap();
|
||||
countdown
|
||||
.resume(resume_time)
|
||||
.expect("resume should succeed");
|
||||
assert_eq!(countdown.state(), CountdownState::Running);
|
||||
assert_eq!(countdown.remaining_at(resume_time), Duration::from_secs(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_transition_returns_error() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(5));
|
||||
|
||||
let err = countdown
|
||||
.pause(now)
|
||||
.expect_err("pause should fail from idle");
|
||||
assert_eq!(
|
||||
err,
|
||||
CountdownError::InvalidTransition {
|
||||
from: CountdownState::Idle,
|
||||
action: "pause",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_countdown_reaches_finished() {
|
||||
let now = Instant::now();
|
||||
let mut countdown = Countdown::new("test", Duration::from_secs(2));
|
||||
countdown.start(now).expect("start should succeed");
|
||||
|
||||
let after_two_seconds = now.checked_add(Duration::from_secs(2)).unwrap();
|
||||
countdown.sync_finished_at(after_two_seconds);
|
||||
|
||||
assert_eq!(countdown.state(), CountdownState::Finished);
|
||||
assert_eq!(
|
||||
countdown.remaining_at(after_two_seconds),
|
||||
Duration::from_secs(0)
|
||||
);
|
||||
assert!(countdown.is_finished());
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
@import "@picocss/pico/css/pico.classless.jade.min.css";
|
||||
@import "@picocss/pico/css/pico.jade.min.css";
|
||||
@import "./styles/base.css";
|
||||
@import "./styles/countdown.css";
|
||||
|
||||
Reference in New Issue
Block a user