Skip to content

feat(ui): add Routines page — user-friendly scheduled tasks view#2864

Merged
M3gA-Mind merged 4 commits into
tinyhumansai:mainfrom
graycyrus:feat/routines-page
May 28, 2026
Merged

feat(ui): add Routines page — user-friendly scheduled tasks view#2864
M3gA-Mind merged 4 commits into
tinyhumansai:mainfrom
graycyrus:feat/routines-page

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented May 28, 2026

Summary

  • Adds a new /routines top-level page that presents cron jobs (scheduled tasks) in human-readable, layman-friendly language
  • Converts cron expressions to natural language ("Every day at 7:00 AM"), hides UUIDs, uses toggle switches, color-coded status dots, and collapsible run history
  • Adds a "Your Routines" card on the Home page linking to the new route
  • Developer Options cron page remains unchanged — this is a separate user-facing view
  • Fixes mascot crash when persisted color is unrecognized (fallback to default palette)
  • Fixes observability classifier ordering: session-expired 401 via embedding path was misclassified as BackendUserError (TAURI-RUST-4K5)

What changed

  • New page: app/src/pages/Routines.tsx — fetches and displays scheduled tasks
  • New components: RoutineCard.tsx, RoutineRunHistory.tsx — card-based UI with toggles and expandable history
  • New utility: routineHelpers.tscronToHuman(), formatRoutineName(), formatNextRun(), formatTimeAgo(), formatDuration()
  • Route: /routines added to AppRoutes.tsx as a protected route
  • Home page: "Your Routines" card with active count, links to /routines
  • i18n: 27 new routines.* keys added to en.ts and all 14 locale chunk files
  • Bug fix: getMascotPalette() now falls back to default when color is unrecognized
  • Bug fix: expected_error_kind() ordering — is_session_expired_message now runs before is_embedding_backend_auth_failure

User experience before → after

Before After
0 7 * * * Every day at 7:00 AM
bbc33fa2-9157-... Morning Briefing
*/30 * * * * Every 30 minutes
ok / error Success (green) / Error (red) with status dots
Buried in Developer Options Accessible from Home page card

Test plan

  • pnpm typecheck passes
  • pnpm lint — 0 errors
  • pnpm format:check — clean
  • pnpm i18n:check — all 14 locales in sync, 0 missing keys
  • 20 unit tests for routineHelpers (cronToHuman, formatRoutineName, formatDuration)
  • Visual: Home → "Your Routines" card → click → /routines page renders with human-readable job data

Summary by CodeRabbit

  • New Features

    • Added a Routines page and UI for managing scheduled jobs (next run, enable/disable, run now, and run history)
    • Home shows active routines count with quick navigation to Routines
    • Added routine cards, expandable run history, and formatting helpers for names, schedules, times, and durations
    • Added translation keys for Routines across languages
  • Bug Fixes

    • Fixed mascot color palette fallback
    • Improved classification of session/authorization errors for clearer user messages
  • Tests

    • Added unit tests for routine helper formatting functions

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Routines management feature: cron-format helpers and tests, RoutineCard and RoutineRunHistory components, a Routines page with state/handlers, protected /routines route and Home integration, i18n keys across locales, a mascot palette fallback, and an observability error-classification tweak.

Changes

Routines Feature Implementation

Layer / File(s) Summary
Routine formatting and time utilities
app/src/components/routines/routineHelpers.ts, app/src/components/routines/routineHelpers.test.ts
cronToHuman, formatRoutineName, formatNextRun, formatTimeAgo, and formatDuration helpers with unit tests covering patterns, timezones, and formatting edge cases.
RoutineCard & RoutineRunHistory components
app/src/components/routines/RoutineCard.tsx, app/src/components/routines/RoutineRunHistory.tsx
RoutineCard renders routine metadata, schedule, next-run info, status indicator, enable toggle and run-now action. RoutineRunHistory provides a collapsible per-run history list with status, time-ago, duration, and optional output expansion.
Routines page with state and event handlers
app/src/pages/Routines.tsx
Page manages job list loading/sorting, per-job run caches (runsByJob), busyKeys for in-flight actions, and handlers: loadJobs, handleToggle, handleRunNow, and handleLoadRuns. Renders error/loading/empty UI and a list of RoutineCard instances.
Route registration and Home integration
app/src/AppRoutes.tsx, app/src/pages/Home.tsx
Registers /routines as a protected route and adds an active-routine count card to Home that asynchronously loads enabled routine count and navigates to the Routines page.
i18n support across multiple locales
app/src/lib/i18n/en.ts, app/src/lib/i18n/chunks/{ar-1,bn-1,de-1,en-1,es-1,fr-1,hi-1,id-1,it-1,ko-1,pl-1,pt-1,ru-1,zh-CN-1}.ts
Adds routines.* keys and nav.routines across language chunks; many chunks contain English placeholder values.

Bug Fix

Layer / File(s) Summary
Palette nullish coalescing fallback
app/src/features/human/Mascot/mascotPalette.ts
getMascotPalette now uses ?? YELLOW_PALETTE to fallback when a palette entry is null or undefined.

Observability

Layer / File(s) Summary
SessionExpired classification precedence
src/core/observability.rs
Moves is_session_expired_message earlier in expected_error_kind, removes the later duplicate check, and updates unit tests to reflect changed classification for certain embedding 401 wire forms.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RoutinesPage
  participant TauriCronAPI
  participant RoutineCard
  participant RoutineRunHistory
  User->>RoutinesPage: open /routines
  RoutinesPage->>TauriCronAPI: openhumanCronList()
  TauriCronAPI-->>RoutinesPage: jobs[]
  RoutinesPage->>RoutineCard: render job props (job, busyKeys, handlers)
  User->>RoutineCard: click Run now
  RoutineCard->>RoutinesPage: onRunNow(jobId)
  RoutinesPage->>TauriCronAPI: openhumanCronRun(jobId)
  TauriCronAPI-->>RoutinesPage: run result
  RoutinesPage->>TauriCronAPI: openhumanCronRuns(jobId, 10)
  TauriCronAPI-->>RoutinesPage: runs[]
  RoutinesPage->>RoutineRunHistory: pass runs for display
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • senamakel

Poem

🐰 I nibble cron lines in the night,
I tune the runs to hop just right,
Cards and histories softly sing,
Labels bloom and timers spring,
Hooray — routines awake and bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding a user-friendly Routines page for viewing scheduled tasks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

@graycyrus graycyrus marked this pull request as ready for review May 28, 2026 19:20
@graycyrus graycyrus requested a review from a team May 28, 2026 19:20
@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team. labels May 28, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes May 28, 2026
Copy link
Copy Markdown
Contributor

@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: 3

🤖 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 `@app/src/AppRoutes.tsx`:
- Around line 130-137: The new Route with path "/routines" inside AppRoutes.tsx
(the JSX Route element mounting <ProtectedRoute requireAuth={true}><Routines
/></ProtectedRoute>) violates the established AppRoutes contract; either remove
this Route or update the route contract in the same change set to declare
"/routines" as an allowed route. Locate the Route node for path="/routines" and
either delete it (and any imports of Routines) or modify the
contract/enumeration that documents supported paths (the routing guideline block
in AppRoutes.tsx) to include "/routines", keeping DefaultRedirect and the
existing allowed paths intact.

In `@app/src/components/routines/routineHelpers.ts`:
- Around line 25-190: The helpers return hard-coded English strings; update
cronToHuman, formatRoutineName, formatNextRun, formatTimeAgo, and formatDuration
to accept a translator function (e.g., t: (key: string, opts?: Record<string,
any>) => string) instead of calling useT in-place, replace all user-facing
literals with i18n keys (e.g., 'routine.every_minute',
'routine.every_N_minutes', 'routine.untitled', 'routine.in_less_than_minute',
'routine.just_now', 'routine.duration_ms', etc.) and pass interpolation
variables (numbers, timeStr, tz) to t for pluralization/formatting; ensure
default behavior remains by keeping a no-op fallback translator that returns the
key or formatted string so existing callers aren’t broken and update call sites
to pass the app's useT-provided translator.

In `@app/src/pages/Routines.tsx`:
- Around line 26-27: The single global busyKey state is not concurrency-safe
because parallel per-job actions overwrite it; replace busyKey/setBusyKey with a
concurrent-aware structure (e.g., busyKeys: Set<string> or Map<string, boolean>
stored via useState or useRef) and update all places that currently call
setBusyKey(key) / setBusyKey(null) to instead add/remove the specific job key
(e.g., busyKeys.add(jobId) and busyKeys.delete(jobId)); change UI checks from
busyKey === id to busyKeys.has(id). Update the routines that perform per-job
actions (e.g., loadRuns, runJob and any handlers referenced where busyKey is
used) to manipulate the new busyKeys collection and to clean up only their own
key in finally blocks so parallel requests don’t clobber each other.
🪄 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: ebf97149-155d-4af2-84c6-64aab62cfb88

📥 Commits

Reviewing files that changed from the base of the PR and between 9a95f2f and ce8fb52.

📒 Files selected for processing (23)
  • app/src/AppRoutes.tsx
  • app/src/components/routines/RoutineCard.tsx
  • app/src/components/routines/RoutineRunHistory.tsx
  • app/src/components/routines/routineHelpers.test.ts
  • app/src/components/routines/routineHelpers.ts
  • app/src/features/human/Mascot/mascotPalette.ts
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pl-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Home.tsx
  • app/src/pages/Routines.tsx

Comment thread app/src/AppRoutes.tsx
Comment on lines +25 to +190
export function cronToHuman(schedule: CoreCronSchedule): string {
if (schedule.kind === 'at') {
return `Once at ${new Date(schedule.at).toLocaleString()}`;
}

if (schedule.kind === 'every') {
const ms = schedule.every_ms;
if (ms < 60_000) {
const secs = Math.round(ms / 1000);
return `Every ${secs} second${secs !== 1 ? 's' : ''}`;
}
if (ms < 3_600_000) {
const mins = Math.round(ms / 60_000);
return `Every ${mins} minute${mins !== 1 ? 's' : ''}`;
}
const hrs = Math.round(ms / 3_600_000);
return `Every ${hrs} hour${hrs !== 1 ? 's' : ''}`;
}

// kind === 'cron'
const expr = schedule.expr.trim();
const parts = expr.split(/\s+/);
if (parts.length < 5) return expr;

const [minPart, hourPart, domPart, monPart, dowPart] = parts;

// Every minute: * * * * *
if (minPart === '*' && hourPart === '*' && domPart === '*' && monPart === '*' && dowPart === '*')
return 'Every minute';

// Every N minutes: */N * * * *
const everyMinMatch = minPart.match(/^\*\/(\d+)$/);
if (everyMinMatch && hourPart === '*' && domPart === '*' && monPart === '*' && dowPart === '*') {
const n = parseInt(everyMinMatch[1], 10);
return `Every ${n} minute${n !== 1 ? 's' : ''}`;
}

// Every hour: 0 * * * *
if (minPart === '0' && hourPart === '*' && domPart === '*' && monPart === '*' && dowPart === '*')
return 'Every hour';

// Every N hours: 0 */N * * *
const everyHourMatch = hourPart.match(/^\*\/(\d+)$/);
if (minPart === '0' && everyHourMatch && domPart === '*' && monPart === '*' && dowPart === '*') {
const n = parseInt(everyHourMatch[1], 10);
return `Every ${n} hour${n !== 1 ? 's' : ''}`;
}

// Fixed time patterns (minute and hour are numeric)
const min = parseInt(minPart, 10);
const hour = parseInt(hourPart, 10);
if (isNaN(min) || isNaN(hour)) return expr;

const timeStr = formatHour(hour, min);
const tz = schedule.tz ? ` (${schedule.tz})` : '';

// Every day at H:MM: M H * * *
if (domPart === '*' && monPart === '*' && dowPart === '*') return `Every day at ${timeStr}${tz}`;

// Weekdays: M H * * 1-5
if (domPart === '*' && monPart === '*' && dowPart === '1-5') return `Weekdays at ${timeStr}${tz}`;

// Weekends: M H * * 0,6 or 6,0
if (domPart === '*' && monPart === '*' && (dowPart === '0,6' || dowPart === '6,0'))
return `Weekends at ${timeStr}${tz}`;

// Specific day of week: M H * * D
if (domPart === '*' && monPart === '*' && /^\d$/.test(dowPart)) {
const dayIndex = parseInt(dowPart, 10);
const dayName = WEEKDAY_NAMES[dayIndex];
if (dayName) return `Every ${dayName} at ${timeStr}${tz}`;
}

// Specific day of month: M H D * *
if (monPart === '*' && dowPart === '*' && /^\d{1,2}$/.test(domPart)) {
const day = parseInt(domPart, 10);
const suffix =
day === 1 || day === 21 || day === 31
? 'st'
: day === 2 || day === 22
? 'nd'
: day === 3 || day === 23
? 'rd'
: 'th';
return `On the ${day}${suffix} of every month at ${timeStr}${tz}`;
}

// Fallback
return expr;
}

/**
* Convert a snake_case or kebab-case job name to Title Case.
*/
export function formatRoutineName(name?: string | null): string {
if (!name) return 'Untitled Routine';
return name.replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}

/**
* Format a future ISO timestamp as a friendly relative string.
*/
export function formatNextRun(iso: string): string {
const target = new Date(iso);
const now = new Date();
const diffMs = target.getTime() - now.getTime();

if (diffMs < 0) return target.toLocaleString();

const diffMin = Math.round(diffMs / 60_000);
if (diffMin < 1) return 'in less than a minute';
if (diffMin < 60) return `in ${diffMin} minute${diffMin !== 1 ? 's' : ''}`;

const diffHrs = Math.floor(diffMs / 3_600_000);
if (diffHrs < 24) return `in ${diffHrs} hour${diffHrs !== 1 ? 's' : ''}`;

// Check if it's tomorrow
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
if (
target.getDate() === tomorrow.getDate() &&
target.getMonth() === tomorrow.getMonth() &&
target.getFullYear() === tomorrow.getFullYear()
) {
return `Tomorrow at ${formatHour(target.getHours(), target.getMinutes())}`;
}

return target.toLocaleString();
}

/**
* Format a past ISO timestamp as relative time (e.g. "2 hours ago").
*/
export function formatTimeAgo(iso: string): string {
const past = new Date(iso);
const now = new Date();
const diffMs = now.getTime() - past.getTime();

if (diffMs < 0) return past.toLocaleString();

const diffSec = Math.floor(diffMs / 1000);
if (diffSec < 60) return 'just now';

const diffMin = Math.floor(diffMs / 60_000);
if (diffMin < 60) return `${diffMin} minute${diffMin !== 1 ? 's' : ''} ago`;

const diffHrs = Math.floor(diffMs / 3_600_000);
if (diffHrs < 24) return `${diffHrs} hour${diffHrs !== 1 ? 's' : ''} ago`;

const diffDays = Math.floor(diffMs / 86_400_000);
if (diffDays < 30) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;

return past.toLocaleDateString();
}

/**
* Format duration_ms into a compact string like "3s" or "1m 12s".
*/
export function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
const totalSec = Math.round(ms / 1000);
if (totalSec < 60) return `${totalSec}s`;
const min = Math.floor(totalSec / 60);
const sec = totalSec % 60;
return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 28, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace hard-coded helper output strings with i18n keys.

cronToHuman, formatRoutineName, formatNextRun, formatTimeAgo, and formatDuration return English literals (e.g., “Every minute”, “Untitled Routine”, “just now”). These are user-visible and bypass translation entirely.

As per coding guidelines: “Every user-visible string in app/src/** … must go through useT() … hard-coded literals are not allowed.”

🤖 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 `@app/src/components/routines/routineHelpers.ts` around lines 25 - 190, The
helpers return hard-coded English strings; update cronToHuman,
formatRoutineName, formatNextRun, formatTimeAgo, and formatDuration to accept a
translator function (e.g., t: (key: string, opts?: Record<string, any>) =>
string) instead of calling useT in-place, replace all user-facing literals with
i18n keys (e.g., 'routine.every_minute', 'routine.every_N_minutes',
'routine.untitled', 'routine.in_less_than_minute', 'routine.just_now',
'routine.duration_ms', etc.) and pass interpolation variables (numbers, timeStr,
tz) to t for pluralization/formatting; ensure default behavior remains by
keeping a no-op fallback translator that returns the key or formatted string so
existing callers aren’t broken and update call sites to pass the app's
useT-provided translator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid concern, but these are pure utility functions that can't use useT() (React hook). The existing CoreJobList.tsx (developer cron page) has the same pattern — raw schedule text from the same kind of formatting. Threading a t function through every helper adds significant complexity for marginal i18n gain (schedule expressions like "Every day at 7:00 AM" are quasi-universal). Will address in a follow-up if localization of schedule descriptions becomes a priority.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@graycyrus — understood. The hook constraint is real, and the t-threading approach is a non-trivial refactor. Given the existing precedent in CoreJobList.tsx and the quasi-universal nature of schedule expressions, deferring this is reasonable. Would you like me to open a GitHub issue to track the follow-up ("Localize routineHelpers schedule/time strings") so it isn't lost?


✏️ Learnings added
Learnt from: graycyrus
Repo: tinyhumansai/openhuman PR: 2864
File: app/src/components/routines/routineHelpers.ts:25-190
Timestamp: 2026-05-28T20:58:50.263Z
Learning: In `app/src/components/routines/routineHelpers.ts`, the formatting helpers (`cronToHuman`, `formatRoutineName`, `formatNextRun`, `formatTimeAgo`, `formatDuration`) intentionally return hard-coded English strings. They are pure TypeScript utility functions (not React components) and therefore cannot call `useT()`. The project treats schedule/time expressions as quasi-universal and defers i18n for these helpers. The developer cron page (`CoreJobList.tsx`) follows the same pattern. Localization of these helpers is a future concern, not a current requirement.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread app/src/pages/Routines.tsx Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@graycyrus graycyrus requested review from M3gA-Mind and sanil-23 May 28, 2026 19:55
@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

2 similar comments
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@graycyrus graycyrus force-pushed the feat/routines-page branch from 86b4d8b to 1526420 Compare May 28, 2026 21:07
@graycyrus graycyrus closed this May 28, 2026
@graycyrus graycyrus reopened this May 28, 2026
@graycyrus graycyrus force-pushed the feat/routines-page branch from b1d5173 to a10f2c9 Compare May 28, 2026 21:14
graycyrus added 4 commits May 29, 2026 03:21
Introduces /routines as a new top-level page that presents cron jobs in
human-readable language. Converts cron expressions to natural language
("Every day at 7:00 AM"), hides UUIDs, shows toggle switches, color-coded
status dots, and collapsible run history. Adds a "Your Routines" card on
the Home page linking to the new route. Developer Options cron page is
unchanged.
getMascotPalette() crashed with "Cannot read properties of undefined
(reading 'bodyFill')" when the persisted mascot color didn't match any
key in the palettes record. Fall back to YELLOW_PALETTE instead.
…th catch-all

The `is_embedding_backend_auth_failure` predicate (lowercased substring
match) was running before `is_session_expired_message`, causing the
OpenHuman backend's "Invalid token" 401 via the embedding path
(TAURI-RUST-4K5) to be classified as BackendUserError instead of
SessionExpired. Move session-expired check earlier so its strict
conjunctive anchor takes precedence; third-party BYO-key 401s still
fall through to the embedding catch-all.
Addresses CodeRabbit review: a single global busyKey was not
concurrency-safe across parallel per-job actions. Now each job's busy
state is tracked independently via a Set, so toggling job A doesn't
clobber the loading state of job B.
@M3gA-Mind M3gA-Mind force-pushed the feat/routines-page branch from a10f2c9 to d10224e Compare May 28, 2026 21:54
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

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

LGTM. Rebased onto upstream/main, resolved observability.rs conflict by keeping upstream's unified SessionExpired classification for both wire shapes. All quality checks pass: typecheck, lint (0 errors), format:check, i18n:check.

Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

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

All three CodeRabbit CHANGES_REQUESTED items have been addressed:

  1. /routines route contract@graycyrus correctly noted CLAUDE.md's route listing is descriptive (a snapshot), not a closed contract. The new /routines route follows the exact same <Route> pattern as every other protected route. Non-blocking.

  2. Hard-coded strings in routineHelpers.ts — The concern is valid but the hook constraint is real: pure utility functions can't call useT(). The existing CoreJobList.tsx developer page already uses the same pattern for schedule text. CodeRabbit acknowledged this and agreed to defer. Non-blocking.

  3. busyKey concurrency — Fixed in commit 27b1603 by replacing the single busyKey: string | null with busyKeys: Set<string>, so each job's busy state is tracked independently. CodeRabbit confirmed the fix is correct.

The Routines feature itself is clean: good UX for a previously buried capability, i18n keys in all 14 locale chunk files, 20 unit tests for the helpers, and the two bug fixes (mascot palette fallback + observability classifier ordering) are straightforward and well-tested.

Approving. ✅

@M3gA-Mind M3gA-Mind dismissed coderabbitai[bot]’s stale review May 28, 2026 22:09

All three actionable items addressed: busyKey concurrency fixed (Set), route contract note addressed by author, i18n helper precedent acknowledged. Dismissing stale CHANGES_REQUESTED.

@M3gA-Mind M3gA-Mind merged commit 78385d5 into tinyhumansai:main May 28, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants