From 86412bff36f1e87824068c01ce4bde838c0a0698 Mon Sep 17 00:00:00 2001 From: ponbac Date: Tue, 24 Mar 2026 12:10:07 +0100 Subject: [PATCH] kleer plan --- KLEER_LEARNINGS.md | 157 ++++++++++ KLEER_PRD.json | 693 +++++++++++++++++++++++++++++++++++++++++++++ KLEER_PROMPT.md | 141 +++++++++ 3 files changed, 991 insertions(+) create mode 100644 KLEER_LEARNINGS.md create mode 100644 KLEER_PRD.json create mode 100644 KLEER_PROMPT.md diff --git a/KLEER_LEARNINGS.md b/KLEER_LEARNINGS.md new file mode 100644 index 0000000..105605b --- /dev/null +++ b/KLEER_LEARNINGS.md @@ -0,0 +1,157 @@ +# Learnings — Kleer Cutover + +Living document for durable discoveries, constraints, and implementation guidance during the Kleer migration. + +--- + +## Product And Scope Decisions + +- Milltime is being fully removed. There is no backward-compatibility period and no dual-provider support in this branch. +- Toki remains a project-based work logging UI in the first Kleer release. +- Kleer events without `client-project` exist, but they will be skipped rather than modeled in Toki. +- The old `AttestLevel(None/Week/Month)` model is being replaced with a direct status model: + - `open` + - `approved` + - `certified` +- Weekly stats stay, but flex/komptid is removed until a documented balance endpoint is confirmed. + +## Codebase Patterns To Reuse + +### Backend + +- The time-tracking backend already uses the right architecture for this cutover: + - domain models and ports in `toki-api/src/domain/` + - provider adapter in `toki-api/src/adapters/outbound/` + - per-request service creation in `toki-api/src/factory.rs` +- The composition root is the only place that should know about concrete provider types. +- Timer history storage stays local and should not be redesigned for this migration. +- The current time-tracking flow is the reference for request-scoped service creation, but all Milltime-specific auth, cookies, and encryption should be deleted. + +### Frontend + +- Time tracking state currently assumes cookie-backed provider auth and provider-specific copy. +- Query and mutation types live in: + - `app/src/lib/api/queries/time-tracking.ts` + - `app/src/lib/api/mutations/time-tracking.ts` +- The status model is used in multiple time-tracking UI components, not just the API types. Expect coordinated changes in: + - list view + - timeline view + - lock warnings + - stats + +## Kleer API Findings + +### Authentication + +- Kleer authentication is token-based and per-request. +- There is no session concept in the API docs. +- Preferred auth transport is the `X-token` header. +- Credentials needed by Toki: + - `token` + - `companyId` + - optional `baseUrl` for test environment support + +### Environments + +- Production API base URL: `https://api.kleer.se/v1` +- Test API base URL: `https://test-api.kleer.se/v1` +- The docs state the same token/login can be used in both environments. + +### Confirmed Time-Tracking Endpoints + +- `GET /v1/company/{companyId}/user/me` +- `GET /v1/company/{companyId}/client-project?filter=active` +- `GET /v1/company/{companyId}/activity` +- `PUT /v1/company/{companyId}/event` +- `GET /v1/company/{companyId}/event` +- `GET /v1/company/{companyId}/event/{eventId}` +- `POST /v1/company/{companyId}/event/{eventId}` +- `DELETE /v1/company/{companyId}/event/{eventId}` +- `GET /v1/company/{companyId}/event/statuses` + +### Confirmed Payroll Endpoint For Weekly Stats + +- `GET /v1/company/{companyId}/payroll/user/{userId}/schedule/{startDate}/to/{endDate}` +- This returns `payroll-user-schedule-metadatas` entries with: + - `date` + - `level-of-employment` + - `gross-hours` + - `net-hours` + - `actual-hours` +- Use `net-hours` as the scheduled-hours source for Toki weekly stats. + +### Event Model Findings + +- `event-writable` in the XSD includes: + - `foreign-id` + - `user` + - `activity` + - optional `client-project` + - optional `child` + - `date` + - `hours` + - `comment` + - optional `internal-comment` +- `client-project` is optional. This is why projectless events must be explicitly handled. +- The documented event statuses are already the right product concept: + - `Open` + - `Approved` + - `Certified` + +### Project And Activity Restrictions + +- Kleer projects can restrict activities at the project level. +- Projects can also define user-specific allowed activities. +- Toki activity loading should not just return all activities for every project; it should respect those project restrictions. + +## Migration-Specific Guidance + +- Use JSON only. Do not add XML support in Toki. +- Keep provider response/request structs in the `kleer/` crate, not in domain code. +- Prefer direct status handling end-to-end: + - backend domain field `status` + - frontend field `status` + - API values `open | approved | certified` +- Do not synthesize flex/komptid from worked minus scheduled hours. +- Do not keep the Milltime route semantics if they only existed to work around Milltime behavior. Example: edit flows can update the same Kleer event id directly. + +## Relevant Repo Touchpoints + +### Backend + +- `toki-api/src/factory.rs` +- `toki-api/src/router.rs` +- `toki-api/src/main.rs` +- `toki-api/src/domain/models/timer.rs` +- `toki-api/src/domain/ports/outbound/time_tracking.rs` +- `toki-api/src/domain/services/time_tracking.rs` +- `toki-api/src/adapters/inbound/http/time_tracking.rs` +- `toki-api/src/adapters/inbound/http/responses.rs` +- `toki-api/src/routes/time_tracking/authenticate.rs` +- `toki-api/src/routes/time_tracking/calendar.rs` + +### Frontend + +- `app/src/lib/api/queries/time-tracking.ts` +- `app/src/lib/api/mutations/time-tracking.ts` +- `app/src/hooks/useTimeTrackingStore.tsx` +- `app/src/hooks/useTimeTrackingData.tsx` +- `app/src/routes/_layout/time-tracking/index.tsx` +- `app/src/routes/_layout/time-tracking/-components/time-entries-list.tsx` +- `app/src/routes/_layout/time-tracking/-components/timeline-view.tsx` +- `app/src/routes/_layout/time-tracking/-components/not-locked-alert.tsx` +- `app/src/routes/_layout/time-tracking/-components/time-stats.tsx` + +## Verification Commands + +```bash +cargo check -p kleer +SQLX_OFFLINE=true just check +just tsc +just lint +just check-all +``` + +## Update Log + +Add new durable learnings here as implementation progresses. diff --git a/KLEER_PRD.json b/KLEER_PRD.json new file mode 100644 index 0000000..505608f --- /dev/null +++ b/KLEER_PRD.json @@ -0,0 +1,693 @@ +{ + "meta": { + "title": "Kleer Cutover With Full Milltime Removal", + "slug": "kleer-cutover", + "created_at": "2026-03-24", + "last_updated": "2026-03-24", + "status": "planned", + "owner": "toki2", + "branch_strategy": "Implement on a dedicated branch and release when Milltime is shut down.", + "backwards_compatibility_required": false + }, + "summary": { + "goal": "Replace Milltime entirely with Kleer for time tracking while keeping Toki's core project-based time logging flows intact.", + "in_scope": [ + "Kleer token/companyId authentication", + "Kleer-backed projects, activities, timer save, time entry CRUD, and lock status", + "Weekly stats backed by Kleer payroll schedule summaries", + "Full Milltime code, cookie, config, and copy removal" + ], + "out_of_scope": [ + "Dual-provider support", + "Milltime backward compatibility", + "Creating Kleer projects or activities from Toki", + "Showing projectless or absence-only Kleer events in Toki", + "Flex/komptid support before a documented balance endpoint exists" + ] + }, + "current_state": { + "completed_task_ids": [], + "in_progress_task_ids": [], + "blocked_task_ids": [], + "next_recommended_task_id": "task-001", + "notes": [ + "Planning is complete.", + "Implementation has not started yet.", + "If repo state diverges from this file, trust the repo and update this file." + ] + }, + "decisions": { + "provider_strategy": "Milltime is fully removed from backend, frontend, workspace, config, and docs.", + "auth": { + "request_payload": { + "token": "string", + "companyId": "string", + "baseUrl": "string | optional" + }, + "validation_strategy": "Validate credentials by calling GET /company/{companyId}/user/me.", + "cookie_strategy": { + "provider_cookie": "tt_provider=kleer", + "token_cookie": "tt_kleer_token", + "company_id_cookie": "tt_kleer_company_id", + "base_url_cookie": "tt_kleer_base_url", + "http_only": true, + "secure_outside_localhost": true, + "path": "/" + } + }, + "event_scope": { + "projectless_events": "skip", + "reason": "Toki remains a project-based work logging UI in the first Kleer release." + }, + "status_model": { + "replace_type": "AttestLevel", + "new_type_name_backend": "TimeEntryStatus", + "new_type_name_frontend": "TimeEntryStatus", + "api_field_name": "status", + "api_string_values": [ + "open", + "approved", + "certified" + ], + "editability_rule": "Only entries with status=open are editable.", + "lock_rule": "Entries with status=approved or status=certified are treated as locked." + }, + "weekly_stats": { + "keep_stats_panel": true, + "remove_flex": true, + "source_of_truth": { + "worked_hours": "Kleer time events", + "scheduled_hours": "Kleer payroll schedule summary net-hours" + }, + "api_contract": { + "workedHours": "number", + "scheduledHours": "number", + "remainingHours": "number" + } + }, + "transport": { + "format": "json", + "auth_header": "X-token", + "default_base_url": "https://api.kleer.se/v1", + "test_base_url": "https://test-api.kleer.se/v1" + } + }, + "api_changes": [ + { + "id": "api-authenticate", + "endpoint": "POST /time-tracking/authenticate", + "current": { + "username": "string", + "password": "string" + }, + "new": { + "token": "string", + "companyId": "string", + "baseUrl": "string | optional" + } + }, + { + "id": "api-time-entries-status", + "endpoint": "GET /time-tracking/time-entries", + "current_field": "attestLevel", + "new_field": "status", + "new_values": [ + "open", + "approved", + "certified" + ] + }, + { + "id": "api-weekly-stats", + "endpoint": "GET /time-tracking/time-info", + "note": "Route path can stay the same, but the response contract changes because backward compatibility is not required.", + "current_fields": [ + "periodTimeLeft", + "workedPeriodTime", + "scheduledPeriodTime", + "workedPeriodWithAbsenceTime", + "flexTimeCurrent" + ], + "new_fields": [ + "workedHours", + "scheduledHours", + "remainingHours" + ] + } + ], + "learnings": [ + { + "id": "learning-auth-no-session", + "category": "api", + "summary": "Kleer authentication is per-request and token-based; there is no session concept.", + "details": "Kleer docs state auth is done with an API token on every request, preferably via the X-token header." + }, + { + "id": "learning-event-writable-shape", + "category": "api", + "summary": "Kleer event writes are project/activity/date/hour/comment based.", + "details": "The XSD event-writable type includes foreign-id, user, activity, optional client-project, optional child, date, hours, comment, and optional internal-comment." + }, + { + "id": "learning-status-is-direct", + "category": "api", + "summary": "Kleer event statuses are already the right product concept.", + "details": "Kleer status values are Open, Approved, and Certified; they should be preserved directly instead of being mapped to None, Week, and Month." + }, + { + "id": "learning-projectless-events-exist", + "category": "api", + "summary": "Kleer events can omit client-project.", + "details": "The XSD marks client-project as optional for events. The first Toki Kleer release will skip those events." + }, + { + "id": "learning-project-activity-restrictions", + "category": "api", + "summary": "Project activity access is not just a flat activity list.", + "details": "Client projects can define project-wide allowed activities and user-specific activity restrictions; activities shown in Toki should honor those restrictions." + }, + { + "id": "learning-schedule-summary-exists", + "category": "api", + "summary": "Kleer exposes payroll schedule summaries that can back weekly stats.", + "details": "GET /payroll/user/:userId/schedule/:startDate/to/:endDate returns payroll-user-schedule-metadatas with gross-hours, net-hours, and actual-hours." + }, + { + "id": "learning-no-documented-flex-endpoint", + "category": "api", + "summary": "No documented flex/komptid balance endpoint has been confirmed.", + "details": "The initial Kleer release should remove flex from Toki instead of synthesizing misleading values." + }, + { + "id": "learning-test-env", + "category": "api", + "summary": "Kleer provides a test API environment.", + "details": "The docs list production at https://api.kleer.se/v1 and test at https://test-api.kleer.se/v1." + } + ], + "phases": [ + { + "id": "phase-1", + "title": "Foundation And Provider Cutover", + "goal": "Replace Milltime wiring with Kleer wiring and establish the new auth/session model." + }, + { + "id": "phase-2", + "title": "Kleer Client And Adapter", + "goal": "Implement the raw Kleer client and the provider adapter behind the existing outbound port." + }, + { + "id": "phase-3", + "title": "Domain And API Model Changes", + "goal": "Replace Milltime-era status and stats models with direct Kleer-aligned types." + }, + { + "id": "phase-4", + "title": "Frontend Cutover", + "goal": "Update auth, types, status logic, and weekly stats UI to the Kleer model." + }, + { + "id": "phase-5", + "title": "Deletion, Cleanup, And Verification", + "goal": "Remove Milltime completely and verify the Kleer flow end-to-end." + } + ], + "tasks": [ + { + "id": "task-001", + "phase_id": "phase-1", + "title": "Create the Kleer crate and wire it into the workspace", + "status": "pending", + "depends_on": [], + "repo_paths": [ + "Cargo.toml", + "kleer/Cargo.toml", + "kleer/src/lib.rs" + ], + "details": [ + "Add a new workspace member `kleer/`.", + "Set up base dependencies for reqwest, serde, serde_json, time, and tracing as needed.", + "Do not keep using the `milltime/` crate for any new code." + ], + "acceptance_criteria": [ + "The workspace builds with the new `kleer` crate present.", + "No runtime code depends on `milltime` for new functionality." + ], + "verification": [ + "cargo check -p kleer" + ] + }, + { + "id": "task-002", + "phase_id": "phase-1", + "title": "Replace Milltime factory wiring with Kleer factory wiring", + "status": "pending", + "depends_on": [ + "task-001" + ], + "repo_paths": [ + "toki-api/src/factory.rs", + "toki-api/src/router.rs", + "toki-api/src/app_state.rs" + ], + "details": [ + "Delete `MilltimeServiceFactory`.", + "Add `KleerServiceFactory` that creates the time tracking service from Kleer cookies.", + "Wire the router/app state to instantiate and use the new factory." + ], + "acceptance_criteria": [ + "The time tracking factory in app state is Kleer-based.", + "Router creation does not reference Milltime types." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-003", + "phase_id": "phase-1", + "title": "Change the time tracking auth flow to token/companyId", + "status": "pending", + "depends_on": [ + "task-002" + ], + "repo_paths": [ + "toki-api/src/adapters/inbound/http/time_tracking.rs", + "toki-api/src/routes/time_tracking/authenticate.rs", + "toki-api/src/factory.rs" + ], + "details": [ + "Change the factory trait auth method signature from username/password to token/companyId/baseUrl.", + "Validate credentials via `user/me`.", + "Set secure Kleer cookies: provider, token, company id, optional base URL." + ], + "acceptance_criteria": [ + "POST /time-tracking/authenticate accepts Kleer credentials.", + "Successful auth produces Kleer cookies only." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-004", + "phase_id": "phase-2", + "title": "Implement the shared Kleer HTTP client and error mapping", + "status": "pending", + "depends_on": [ + "task-001" + ], + "repo_paths": [ + "kleer/src/client.rs", + "kleer/src/types.rs" + ], + "details": [ + "Add a client wrapper that accepts base URL, token, and company id context.", + "Use JSON requests/responses and the X-token header.", + "Centralize error mapping for unauthorized, forbidden, not found, and generic non-2xx responses." + ], + "acceptance_criteria": [ + "The raw client can issue authenticated JSON requests to Kleer.", + "Errors are normalized into actionable client-side/provider-side failures." + ], + "verification": [ + "cargo check -p kleer" + ] + }, + { + "id": "task-005", + "phase_id": "phase-2", + "title": "Add typed Kleer endpoints for user, projects, activities, events, statuses, and schedule summary", + "status": "pending", + "depends_on": [ + "task-004" + ], + "repo_paths": [ + "kleer/src/client.rs", + "kleer/src/types.rs" + ], + "details": [ + "Implement methods for `user/me`, client-project list, activity list, event list, event statuses, event create/update/delete, and payroll schedule summary.", + "Keep provider response types in the `kleer` crate." + ], + "acceptance_criteria": [ + "All endpoints needed by the migration exist as typed methods in the raw client." + ], + "verification": [ + "cargo check -p kleer" + ] + }, + { + "id": "task-006", + "phase_id": "phase-2", + "title": "Implement KleerAdapter behind the existing TimeTrackingClient port", + "status": "pending", + "depends_on": [ + "task-005" + ], + "repo_paths": [ + "toki-api/src/adapters/outbound/kleer/mod.rs", + "toki-api/src/adapters/outbound/kleer/conversions.rs", + "toki-api/src/adapters/outbound/mod.rs" + ], + "details": [ + "Create `KleerAdapter` as the new outbound adapter.", + "Keep domain code provider-agnostic.", + "Do all provider-to-domain mapping in the adapter layer." + ], + "acceptance_criteria": [ + "The backend has a Kleer outbound adapter that implements TimeTrackingClient." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-007", + "phase_id": "phase-2", + "title": "Implement project and activity filtering logic from Kleer project restrictions", + "status": "pending", + "depends_on": [ + "task-006" + ], + "repo_paths": [ + "toki-api/src/adapters/outbound/kleer/mod.rs", + "toki-api/src/adapters/outbound/kleer/conversions.rs" + ], + "details": [ + "Map active client projects into Toki projects.", + "Filter activities per project based on project-level allowed activities.", + "If user-specific activity restrictions exist on the project, intersect with them for the current user." + ], + "acceptance_criteria": [ + "The project activity picker only shows activities allowed for the selected project and current user." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-008", + "phase_id": "phase-3", + "title": "Replace AttestLevel with direct TimeEntryStatus across the backend", + "status": "pending", + "depends_on": [ + "task-006" + ], + "repo_paths": [ + "toki-api/src/domain/models/timer.rs", + "toki-api/src/adapters/inbound/http/responses.rs" + ], + "details": [ + "Replace `AttestLevel` with `TimeEntryStatus`.", + "Change `TimeEntry.attest_level` to `TimeEntry.status`.", + "Serialize status as string values: open, approved, certified." + ], + "acceptance_criteria": [ + "The backend no longer exposes or uses AttestLevel.", + "Time entries expose `status` instead of `attestLevel`." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-009", + "phase_id": "phase-3", + "title": "Map Kleer events and statuses into Toki time entries", + "status": "pending", + "depends_on": [ + "task-007", + "task-008" + ], + "repo_paths": [ + "toki-api/src/adapters/outbound/kleer/mod.rs", + "toki-api/src/adapters/outbound/kleer/conversions.rs" + ], + "details": [ + "Fetch current user id from `user/me`.", + "Fetch events for the selected range.", + "Filter out events without `client-project`.", + "Resolve project and activity names from cached lookup maps.", + "Fetch event statuses and map OPEN/APPROVED/CERTIFIED directly into TimeEntryStatus." + ], + "acceptance_criteria": [ + "Only project-linked work entries are returned by the backend.", + "Each time entry has a direct Kleer-aligned status." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-010", + "phase_id": "phase-3", + "title": "Implement create, update, and delete flows against Kleer events", + "status": "pending", + "depends_on": [ + "task-009" + ], + "repo_paths": [ + "toki-api/src/adapters/outbound/kleer/mod.rs", + "toki-api/src/domain/services/time_tracking.rs", + "toki-api/src/routes/time_tracking/calendar.rs" + ], + "details": [ + "Create Kleer event payloads from Toki create/edit requests.", + "Use direct update-by-id instead of Milltime recreate-on-date-change behavior.", + "Keep timer history persistence semantics unchanged." + ], + "acceptance_criteria": [ + "Create/edit/delete endpoints operate against Kleer event ids.", + "Timer history still records saved timer relationships." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-011", + "phase_id": "phase-3", + "title": "Replace TimeInfo with a simpler WeeklyStats model", + "status": "pending", + "depends_on": [ + "task-005", + "task-009" + ], + "repo_paths": [ + "toki-api/src/domain/models/timer.rs", + "toki-api/src/adapters/inbound/http/responses.rs", + "toki-api/src/routes/time_tracking/calendar.rs", + "toki-api/src/domain/ports/outbound/time_tracking.rs" + ], + "details": [ + "Replace Milltime-era TimeInfo with a new weekly stats shape.", + "Use workedHours, scheduledHours, and remainingHours only." + ], + "acceptance_criteria": [ + "The backend no longer computes or exposes flexTimeCurrent or other Milltime-specific stats fields." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-012", + "phase_id": "phase-3", + "title": "Back weekly stats with Kleer payroll schedule summaries", + "status": "pending", + "depends_on": [ + "task-011" + ], + "repo_paths": [ + "toki-api/src/adapters/outbound/kleer/mod.rs" + ], + "details": [ + "Call the Kleer payroll schedule summary endpoint for the requested range.", + "Use net-hours as the scheduled-hours source.", + "Compute remainingHours as max(0, scheduledHours - workedHours)." + ], + "acceptance_criteria": [ + "Weekly stats reflect Kleer schedule expectations and event totals.", + "No flex value is synthesized." + ], + "verification": [ + "SQLX_OFFLINE=true just check" + ] + }, + { + "id": "task-013", + "phase_id": "phase-4", + "title": "Cut frontend auth and session handling over to Kleer", + "status": "pending", + "depends_on": [ + "task-003" + ], + "repo_paths": [ + "app/src/hooks/useTimeTrackingStore.tsx", + "app/src/lib/api/mutations/time-tracking.ts", + "app/src/routes/_layout/time-tracking/index.tsx" + ], + "details": [ + "Replace username/password form fields with API token and company ID.", + "Remove all `mt_*` cookie detection and cleanup.", + "Update user-facing auth copy from Milltime to Kleer." + ], + "acceptance_criteria": [ + "The frontend authenticates using Kleer credentials only.", + "No Milltime auth copy or cookie logic remains." + ], + "verification": [ + "just tsc", + "just lint" + ] + }, + { + "id": "task-014", + "phase_id": "phase-4", + "title": "Replace frontend AttestLevel usage with TimeEntryStatus", + "status": "pending", + "depends_on": [ + "task-008", + "task-013" + ], + "repo_paths": [ + "app/src/lib/api/queries/time-tracking.ts", + "app/src/routes/_layout/time-tracking/-components/time-entries-list.tsx", + "app/src/routes/_layout/time-tracking/-components/timeline-view.tsx", + "app/src/routes/_layout/time-tracking/-components/not-locked-alert.tsx" + ], + "details": [ + "Replace the frontend enum/type and API field from `attestLevel` to `status`.", + "Update editability checks to use `status === open`.", + "Update lock checks to use approved/certified." + ], + "acceptance_criteria": [ + "No frontend code references AttestLevel or attestLevel.", + "Editing and lock warnings use direct Kleer statuses." + ], + "verification": [ + "just tsc", + "just lint" + ] + }, + { + "id": "task-015", + "phase_id": "phase-4", + "title": "Replace frontend stats types and UI with WeeklyStats", + "status": "pending", + "depends_on": [ + "task-011", + "task-012" + ], + "repo_paths": [ + "app/src/lib/api/queries/time-tracking.ts", + "app/src/routes/_layout/time-tracking/-components/time-stats.tsx" + ], + "details": [ + "Update time tracking query types for the new weekly stats contract.", + "Keep weekly progress and remaining hours UI.", + "Remove the flex UI entirely." + ], + "acceptance_criteria": [ + "The stats panel renders worked, scheduled, and remaining hours only.", + "No flex text, field, or card remains." + ], + "verification": [ + "just tsc", + "just lint" + ] + }, + { + "id": "task-016", + "phase_id": "phase-5", + "title": "Delete Milltime code, env assumptions, and copy from the repo", + "status": "pending", + "depends_on": [ + "task-002", + "task-003", + "task-013", + "task-014", + "task-015" + ], + "repo_paths": [ + "milltime/", + "toki-api/src/main.rs", + "toki-api/src/adapters/outbound/milltime/", + "README.md", + "AGENTS.md", + "app/src/routes/_layout/time-tracking/index.tsx", + "app/src/routes/_layout/time-tracking/-components/not-locked-alert.tsx" + ], + "details": [ + "Remove the Milltime crate and any workspace references.", + "Remove Milltime-specific outbound adapter files.", + "Remove Milltime env requirements, docs, and user-facing provider names." + ], + "acceptance_criteria": [ + "The repo contains no active Milltime runtime code.", + "The backend starts without Milltime env vars." + ], + "verification": [ + "SQLX_OFFLINE=true just check", + "just tsc", + "just lint" + ] + }, + { + "id": "task-017", + "phase_id": "phase-5", + "title": "Run full verification and update the Kleer handoff files", + "status": "pending", + "depends_on": [ + "task-016" + ], + "repo_paths": [ + "KLEER_PRD.json", + "KLEER_LEARNINGS.md", + "KLEER_PROMPT.md" + ], + "details": [ + "Run backend and frontend verification commands.", + "Update task statuses and next recommended task in this PRD.", + "Append any new durable implementation discoveries to KLEER_LEARNINGS.md." + ], + "acceptance_criteria": [ + "The PRD accurately reflects implementation progress.", + "The learnings doc contains all durable findings discovered during implementation." + ], + "verification": [ + "just check-all" + ] + } + ], + "verification_commands": [ + "cargo check -p kleer", + "SQLX_OFFLINE=true just check", + "just tsc", + "just lint", + "just check-all" + ], + "sources": [ + { + "label": "Issue #74", + "url": "https://github.com/ponbac/toki2/issues/74" + }, + { + "label": "Kleer API docs", + "url": "https://api-doc.kleer.se/" + }, + { + "label": "Kleer Postman metadata", + "url": "https://api-doc.kleer.se/view/metadata/2s9YywgL6m" + }, + { + "label": "Kleer Postman collection", + "url": "https://api-doc.kleer.se/api/collections/11922723/2s9YywgL6m?environment=11922723-d718b016-0e8b-4900-85df-bbba28471ce1&segregateAuth=true&versionTag=latest" + }, + { + "label": "Kleer XSD", + "url": "https://api.kleer.se/v1/xsd" + } + ] +} diff --git a/KLEER_PROMPT.md b/KLEER_PROMPT.md new file mode 100644 index 0000000..22d096a --- /dev/null +++ b/KLEER_PROMPT.md @@ -0,0 +1,141 @@ +# Agent Prompt — Kleer Cutover + +This prompt is idempotent. Any agent can pick this up and continue from the current Kleer PRD state instead of restarting discovery. + +--- + +## Goal + +Complete the Toki2 migration from Milltime to Kleer. + +Non-negotiables: + +- Remove Milltime completely. +- Do not build dual-provider support. +- Keep Toki project-based for the first Kleer release. +- Skip projectless/absence Kleer events. +- Use direct Kleer statuses: + - `open` + - `approved` + - `certified` +- Keep weekly worked/scheduled/remaining stats. +- Remove flex/komptid until a documented balance endpoint exists. + +## How To Start + +1. Read `AGENTS.md` and `CLAUDE.md` for repo conventions and workflow constraints. +2. Read `KLEER_PRD.json` fully. +3. Read `KLEER_LEARNINGS.md` fully. +4. Check the repo state with `jj status`. +5. In `KLEER_PRD.json`, inspect: + - `current_state` + - `tasks` + - `decisions` +6. Start with the first task whose: + - `status` is `pending` + - all `depends_on` tasks are done in repo reality or marked complete in the PRD + +If the repo state and PRD disagree, trust the repo and update the PRD before continuing. + +## Resume Rule + +Do not restart planning from scratch. + +Resume from the current PRD state by: + +- checking which tasks are already done +- updating `KLEER_PRD.json` task statuses as work progresses +- updating `current_state.next_recommended_task_id` +- appending new durable findings to `KLEER_LEARNINGS.md` + +## Execution Order + +Follow the task dependency graph in `KLEER_PRD.json`. + +Default phase order: + +1. Foundation and provider cutover +2. Kleer client and adapter +3. Domain and API model changes +4. Frontend cutover +5. Deletion, cleanup, and verification + +## Key Implementation Constraints + +### Backend + +- Keep the hexagonal time-tracking architecture. +- Domain code must stay provider-agnostic. +- Provider request/response types belong in the `kleer/` crate. +- Composition root is the only place that should know about Kleer concrete types. +- Timer history storage stays local; do not redesign it. + +### Frontend + +- Replace all Milltime auth and provider copy. +- Replace `attestLevel` with `status`. +- Replace `AttestLevel` with `TimeEntryStatus`. +- Editability and lock logic must use direct Kleer statuses. +- Stats UI must use the new weekly stats shape and contain no flex UI. + +### Version Control And Verification + +- Prefer `jj` over `git`. +- Use the smallest meaningful verification after each task or phase. +- Final verification target: + +```bash +just check-all +``` + +## Recommended Working Loop + +1. Pick the next task from `KLEER_PRD.json`. +2. Implement it fully. +3. Run the task’s listed verification commands. +4. Update task status in `KLEER_PRD.json`. +5. Add any durable discovery to `KLEER_LEARNINGS.md`. +6. Move `current_state.next_recommended_task_id` forward. + +## Files That Matter Most + +### Planning / Handoff + +- `KLEER_PRD.json` +- `KLEER_LEARNINGS.md` +- `KLEER_PROMPT.md` + +### Backend + +- `toki-api/src/factory.rs` +- `toki-api/src/router.rs` +- `toki-api/src/main.rs` +- `toki-api/src/domain/models/timer.rs` +- `toki-api/src/domain/ports/outbound/time_tracking.rs` +- `toki-api/src/domain/services/time_tracking.rs` +- `toki-api/src/adapters/inbound/http/time_tracking.rs` +- `toki-api/src/adapters/inbound/http/responses.rs` +- `toki-api/src/routes/time_tracking/authenticate.rs` +- `toki-api/src/routes/time_tracking/calendar.rs` + +### Frontend + +- `app/src/lib/api/queries/time-tracking.ts` +- `app/src/lib/api/mutations/time-tracking.ts` +- `app/src/hooks/useTimeTrackingStore.tsx` +- `app/src/routes/_layout/time-tracking/index.tsx` +- `app/src/routes/_layout/time-tracking/-components/time-entries-list.tsx` +- `app/src/routes/_layout/time-tracking/-components/timeline-view.tsx` +- `app/src/routes/_layout/time-tracking/-components/not-locked-alert.tsx` +- `app/src/routes/_layout/time-tracking/-components/time-stats.tsx` + +## Done Condition + +The work is complete when: + +- Milltime code and assumptions are gone +- Kleer auth and time entry flows work +- Toki exposes direct Kleer statuses +- weekly stats are backed by Kleer payroll schedule summaries +- flex is removed +- `KLEER_PRD.json` and `KLEER_LEARNINGS.md` accurately reflect the final implementation state