Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 106e42a81d | |||
| 3c4c28a1ac | |||
| 119661628e | |||
| 336e6d48c5 |
55
.cursor/skills/project-release/SKILL.md
Normal file
55
.cursor/skills/project-release/SKILL.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: project-release
|
||||
description: Performs a project release by updating CHANGELOG for the new version, committing all changes, and pushing a version tag. Use when the user asks to release, cut a release, publish a version, or to update changelog and push a tag.
|
||||
---
|
||||
|
||||
# Project Release
|
||||
|
||||
Release workflow for duty-teller: update changelog, commit, tag, and push. Triggers Gitea Actions (Docker build) on `v*` tags.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Decide the **new version** (e.g. `2.1.0`). Use [Semantic Versioning](https://semver.org/).
|
||||
- Ensure `CHANGELOG.md` has entries under `## [Unreleased]` (or add a short note like "No changes" if intentional).
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Update CHANGELOG.md
|
||||
|
||||
- Replace the `## [Unreleased]` section with a dated release section:
|
||||
- `## [X.Y.Z] - YYYY-MM-DD` (use today's date in `YYYY-MM-DD`).
|
||||
- Leave a new empty `## [Unreleased]` section after it (for future edits).
|
||||
- At the bottom of the file, add the comparison link for the new version:
|
||||
- `[X.Y.Z]: https://github.com/your-org/duty-teller/releases/tag/vX.Y.Z`
|
||||
- (Replace `your-org/duty-teller` with the real repo URL if different.)
|
||||
- Update the `[Unreleased]` link to compare against this release, e.g.:
|
||||
- `[Unreleased]: https://github.com/your-org/duty-teller/compare/vX.Y.Z...HEAD`
|
||||
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); keep existing subsections (Added, Changed, Security, etc.) under the new version.
|
||||
|
||||
### 2. Bump version in pyproject.toml (optional)
|
||||
|
||||
- Set `version = "X.Y.Z"` in `[project]` so it matches the release. Skip if the project does not sync version here.
|
||||
|
||||
### 3. Commit and tag
|
||||
|
||||
- Stage all changes: `git add -A`
|
||||
- Commit with Conventional Commits: `git commit -m "chore(release): vX.Y.Z"`
|
||||
- Create annotated tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`
|
||||
- Push branch: `git push origin main` (or current branch)
|
||||
- Push tag: `git push origin vX.Y.Z`
|
||||
|
||||
Pushing the `v*` tag triggers `.gitea/workflows/docker-build.yml` (Docker build and release).
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] CHANGELOG: `[Unreleased]` → `[X.Y.Z] - YYYY-MM-DD`, new empty `[Unreleased]`, links at bottom updated
|
||||
- [ ] pyproject.toml version set to X.Y.Z (if used)
|
||||
- [ ] `git add -A` && `git commit -m "chore(release): vX.Y.Z"`
|
||||
- [ ] `git tag -a vX.Y.Z -m "Release vX.Y.Z"`
|
||||
- [ ] `git push origin main` && `git push origin vX.Y.Z`
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not push tags from unreleased or uncommitted changelog.
|
||||
- If the repo URL in CHANGELOG links is a placeholder, keep it or ask the user for the correct base URL.
|
||||
60
.cursor/skills/run-tests/SKILL.md
Normal file
60
.cursor/skills/run-tests/SKILL.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: run-tests
|
||||
description: Runs backend (pytest) and frontend (Vitest) tests for the duty-teller project. Use when the user asks to run tests, verify changes, or run pytest/vitest.
|
||||
---
|
||||
|
||||
# Run tests
|
||||
|
||||
## When to use
|
||||
|
||||
- User asks to "run tests", "run the test suite", or "verify tests pass".
|
||||
- After making code changes and user wants to confirm nothing is broken.
|
||||
- User explicitly asks for backend tests (pytest) or frontend tests (vitest/npm test).
|
||||
|
||||
## Backend tests (Python)
|
||||
|
||||
From the **repository root**:
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
If imports fail, set `PYTHONPATH`:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=. pytest
|
||||
```
|
||||
|
||||
- Config: `pyproject.toml` → `[tool.pytest.ini_options]` (coverage on `duty_teller`, 80% gate, asyncio mode).
|
||||
- Tests live in `tests/`.
|
||||
|
||||
## Frontend tests (Next.js / Vitest)
|
||||
|
||||
From the **repository root**:
|
||||
|
||||
```bash
|
||||
cd webapp-next && npm test
|
||||
```
|
||||
|
||||
- Runner: Vitest (`vitest run`); env: jsdom; React Testing Library.
|
||||
- Config: `webapp-next/vitest.config.ts`; setup: `webapp-next/src/test/setup.ts`.
|
||||
|
||||
## Running both
|
||||
|
||||
To run backend and frontend tests in sequence:
|
||||
|
||||
```bash
|
||||
pytest && (cd webapp-next && npm test)
|
||||
```
|
||||
|
||||
If the user did not specify "backend only" or "frontend only", run both and report results for each.
|
||||
|
||||
## Scope
|
||||
|
||||
- **Single file or dir:** `pytest path/to/test_file.py` or `pytest path/to/test_dir/`. For frontend, use Vitest’s path args as per its docs (e.g. under `webapp-next/`).
|
||||
- **Verbosity:** Use `pytest -v` if the user wants more detail.
|
||||
|
||||
## Failures
|
||||
|
||||
- Do not send raw exception strings from tests to the user; summarize failures and point to failing test names/locations.
|
||||
- If pytest fails with import errors, suggest `PYTHONPATH=. pytest` and ensure the venv is activated and dev deps are installed (`pip install -r requirements-dev.txt` or `pip install -e ".[dev]"`).
|
||||
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.0.2] - 2025-03-04
|
||||
|
||||
(No changes documented; release for version sync.)
|
||||
|
||||
## [2.0.0] - 2026-03-03
|
||||
|
||||
### Added
|
||||
@@ -44,5 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Input validation and initData hash verification for Miniapp access.
|
||||
- Optional CORS and init_data_max_age; use env for secrets.
|
||||
|
||||
[Unreleased]: https://github.com/your-org/duty-teller/compare/v2.0.2...HEAD
|
||||
[2.0.2]: https://github.com/your-org/duty-teller/releases/tag/v2.0.2 <!-- placeholder: set to your repo URL when publishing -->
|
||||
[2.0.0]: https://github.com/your-org/duty-teller/releases/tag/v2.0.0 <!-- placeholder: set to your repo URL when publishing -->
|
||||
[0.1.0]: https://github.com/your-org/duty-teller/releases/tag/v0.1.0 <!-- placeholder: set to your repo URL when publishing -->
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "duty-teller"
|
||||
version = "0.1.0"
|
||||
version = "2.0.2"
|
||||
description = "Telegram bot for team duty shift calendar and group reminder"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -112,13 +112,21 @@ export function DayDetailContent({
|
||||
>
|
||||
{t("event_type.duty")}
|
||||
</h3>
|
||||
<ul className="list-[disc] pl-5 m-0 text-[0.9rem] leading-snug space-y-1 [&_li]:flex [&_li]:items-baseline [&_li]:gap-1">
|
||||
<ul className="list-none pl-5 m-0 text-[0.9rem] leading-snug space-y-1 [&_li]:flex [&_li]:items-baseline [&_li]:gap-2">
|
||||
{dutyRows.map((r) => (
|
||||
<li key={r.id}>
|
||||
{r.timePrefix && (
|
||||
<span className="text-muted-foreground">{r.timePrefix} — </span>
|
||||
)}
|
||||
<span className="font-semibold">{r.fullName}</span>
|
||||
<span
|
||||
className="text-muted-foreground -ml-5 mr-0 shrink-0 select-none"
|
||||
aria-hidden
|
||||
>
|
||||
•
|
||||
</span>
|
||||
<span className="inline-flex items-baseline gap-1">
|
||||
{r.timePrefix && (
|
||||
<span className="text-muted-foreground">{r.timePrefix} — </span>
|
||||
)}
|
||||
<span className="font-semibold">{r.fullName}</span>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -136,9 +144,17 @@ export function DayDetailContent({
|
||||
>
|
||||
{t("event_type.unavailable")}
|
||||
</h3>
|
||||
<ul className="list-[disc] pl-5 m-0 text-[0.9rem] leading-snug space-y-1">
|
||||
<ul className="list-none pl-5 m-0 text-[0.9rem] leading-snug space-y-1 [&_li]:flex [&_li]:items-baseline [&_li]:gap-2">
|
||||
{uniqueUnavailable.map((name) => (
|
||||
<li key={name}>{name}</li>
|
||||
<li key={name}>
|
||||
<span
|
||||
className="text-muted-foreground -ml-5 mr-0 shrink-0 select-none"
|
||||
aria-hidden
|
||||
>
|
||||
•
|
||||
</span>
|
||||
{name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
@@ -155,9 +171,17 @@ export function DayDetailContent({
|
||||
>
|
||||
{t("event_type.vacation")}
|
||||
</h3>
|
||||
<ul className="list-[disc] pl-5 m-0 text-[0.9rem] leading-snug space-y-1">
|
||||
<ul className="list-none pl-5 m-0 text-[0.9rem] leading-snug space-y-1 [&_li]:flex [&_li]:items-baseline [&_li]:gap-2">
|
||||
{uniqueVacation.map((name) => (
|
||||
<li key={name}>{name}</li>
|
||||
<li key={name}>
|
||||
<span
|
||||
className="text-muted-foreground -ml-5 mr-0 shrink-0 select-none"
|
||||
aria-hidden
|
||||
>
|
||||
•
|
||||
</span>
|
||||
{name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
@@ -174,9 +198,17 @@ export function DayDetailContent({
|
||||
>
|
||||
{t("hint.events")}
|
||||
</h3>
|
||||
<ul className="list-[disc] pl-5 m-0 text-[0.9rem] leading-snug space-y-1">
|
||||
<ul className="list-none pl-5 m-0 text-[0.9rem] leading-snug space-y-1 [&_li]:flex [&_li]:items-baseline [&_li]:gap-2">
|
||||
{summaries.map((s) => (
|
||||
<li key={String(s)}>{String(s)}</li>
|
||||
<li key={String(s)}>
|
||||
<span
|
||||
className="text-muted-foreground -ml-5 mr-0 shrink-0 select-none"
|
||||
aria-hidden
|
||||
>
|
||||
•
|
||||
</span>
|
||||
{String(s)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -80,7 +80,7 @@ function SheetContent({
|
||||
forceMount={useForceMount ? true : undefined}
|
||||
onAnimationEnd={handleAnimationEnd}
|
||||
className={cn(
|
||||
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=closed]:ease-out data-[state=open]:animate-in data-[state=open]:duration-500",
|
||||
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=closed]:ease-out data-[state=open]:animate-in data-[state=open]:duration-300",
|
||||
side === "right" &&
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
side === "left" &&
|
||||
|
||||
Reference in New Issue
Block a user