Rename a cron group header
How to override the label a group of cron jobs shows in the Cron Jobs view and its sidebar without renaming the project itself — the pencil on the group header, the 80-character limit, what the override does not touch, and how orphaned labels are cleaned up.
What it is
The Cron Jobs view groups jobs two ways — By Project (each group header shows the project's name) or By Frequency (each group header shows the cadence: hourly, daily, every-6-hours, etc.). In project mode, the group header label can feel wrong: a project called "Agent Orchestrator" shows up as a long sidebar header, you may want it to read "Orchestrator" or "Omniscio" inside the cron view without renaming the project everywhere else, and orphan UUIDs from soft-deleted projects sometimes briefly surface as group headers before the next prune sweep cleans them up.
Group rename lets you override the label that the cron view uses for any project group without touching the project's real name. The override is local to your install (lives in settings.cronGroupLabels), applies in both the Cron Jobs view (CronDashboard.tsx) and the Cron Jobs sidebar (CronJobsSidebar.tsx), and is automatically reset back to the canonical project name when you clear it. Frequency-mode headers are not renameable — those labels are derived from the cron expression itself and renaming hourly to "every hour" would just confuse the next reader.
Where to find it
The control lives in the Cron Jobs view — Omniscio's Cron Jobs virtual project in the sidebar — and in the smaller cron sidebar attached to a session that owns cron jobs. In both places the jobs are grouped either By Project or By Frequency, and the rename affordance appears only in the By Project grouping, as a small pencil at the right edge of a group header when you hover it.
How it behaves
How to use it
- Open the Cron Jobs view or sidebar. Either the Cron Jobs virtual project in the Omniscio sidebar, or the cron sidebar attached to a session that has cron jobs.
- Switch to By Project mode. The segmented
By Project / By Frequencycontrol at the top of either surface controls grouping. Rename only appears in By Project. - Hover the group header. A small pencil icon fades in at the right edge of the row when you hover. The icon is also focus-revealed for keyboard users —
Tabto the group header's row, thenTabagain to the rename trigger. - Click the pencil. The header label is replaced by an inline text input pre-filled with the current label (the project name or whatever you previously renamed it to). The input auto-focuses and selects the existing text, so just type to overwrite.
- Save with Enter (or click outside). Pressing Enter, or blurring the input by clicking elsewhere, commits the new label. Pressing Esc cancels without saving.
- Reset by saving an empty value. To go back to the canonical project name, open the rename input, clear it (or just press Enter on whitespace), and commit. The override is removed and the header reverts to
project.name. Saving the same value as the live label also resets — there's no way to "save the current value as an override".
The label is clamped to 80 characters and trimmed before save. Anything longer is silently truncated. The "Unassigned" group (jobs whose project no longer exists) is also renameable using the same flow, in case you want to label your orphan jobs as "To triage" or similar.
What it doesn't do
- Doesn't rename the project itself. Other Omniscio views — the sessions sidebar, project switcher, breadcrumbs, settings — all keep showing the real
project.name. To rename the project everywhere, use the project's three-dot menu → Edit → Name. - Doesn't sync across machines. Overrides are per-install. If you use Omniscio on a desktop and a laptop, you'll need to rename groups on each.
- Doesn't apply in By Frequency mode. Frequency labels (
hourly,daily,weekly, etc.) are derived and never overrideable. The pencil icon is hidden in frequency mode by design. - Doesn't survive project deletion. When you delete a project (three-dot → Delete), Omniscio drops any cron group label override keyed by that project id as part of the cascade — so the override map stays in lockstep with the live project set. There's no "ghost label" left behind for an undelete to recover.
For agents
How it works
The override map
Overrides live in settings.cronGroupLabels, a Record<string, string> keyed by the group key (a project UUID, or the literal string 'Unassigned' for orphan jobs). The schema is registered in updateSettingsSchema (src/shared/ipc-schemas.ts) as z.record(z.string().min(1).max(64), z.string().min(1).max(80)).optional() — empty-string values are rejected at IPC boundary, so the renderer must drop the key (not write '') when resetting.
The resolver — single source of truth
Both the dashboard and the sidebar look up labels through the same resolver hook, useGroupLabelResolver(viewMode), in src/renderer/src/features/cron/cron-group-label.ts. The hook subscribes to the projects and settings.cronGroupLabels slices, then returns a (groupKey) => { label, renameable } closure that:
- In frequency mode, returns
{ label: groupKey, renameable: false }— the cadence string is the label. - In project mode, looks up the override first, then falls back to the live project name, then to a UUID-shortened orphan label (
abc12345…) if neither exists. Always returnsrenameable: truein project mode — even orphans are renameable so you can name an unfamiliar UUID before prune sweeps it away.
The Unassigned sentinel is a hardcoded literal 'Unassigned' shared between the renderer resolver and the cron-store producer (cron-store.ts:408) — drift between the two would silently break the rename round-trip.
The header component
The pencil affordance and inline input live in src/renderer/src/features/cron/CronJobGroupHeader.tsx. The component takes an optional onRename: (newLabel: string) => void prop — when omitted (frequency mode), the pencil and input are not rendered at all. When provided (project mode), the trigger button is hover/focus-revealed via a Tailwind group-hover:opacity-100 chain and e.stopPropagation() on its onClick prevents the toggle button from collapsing the group while you click the pencil.
The input itself stops propagation on onClick/onMouseDown so clicking inside it doesn't toggle the group either. Enter/Escape are handled inside the input's onKeyDown; the input also commits on onBlur, so clicking outside the row saves the same as pressing Enter.
handleCommit runs the trim → slice-to-80 → "is this a reset?" check before calling onRename. The reset condition fires when the trimmed value is empty or equal to the trimmed live label — both cases call onRename('') so the wiring layer can delete the override key. Non-reset values pass through verbatim.
The wiring layer
CronDashboard.tsx and CronJobsSidebar.tsx each define their own handleRenameGroup(groupKey, newLabel) callback. Both follow the same shape:
- Read the current
settings.cronGroupLabelsfrom the settings store (default{}). - If
newLabel === '', delete the key from a copy of the map (skip the IPC entirely if the key wasn't present — idempotent no-op). - Otherwise, set the key to the new label (skip the IPC if the value didn't change — idempotent no-op).
- Call
useSettingsStore.getState().updateSetting('cronGroupLabels', updated)— optimistic patch, rolls back on IPC error.
The two callbacks are intentionally duplicated rather than extracted into a shared helper. The shape is small enough (~10 lines), and centralizing it in cron-group-label.ts would create a renderer→store cycle in the resolver module that the linter would complain about.
Orphan key garbage collection
Two seams reap orphan keys (project UUIDs in the map that no longer correspond to a live project), both calling pruneOrphanCronGroupLabels():
- Startup hook in src/main/index.ts, inside
app.whenReady()after the AHK clear-stale block — runs once per launch, before the renderer paints, so any orphan that accumulated across sessions is gone before the user sees the cron view. - Project delete cascade in src/main/services/project-delete-service.ts, immediately after the
ahkLinkedProjectIdclear — so the override map shrinks the moment a project leaves the live set.
The prune is idempotent: when no orphan keys exist, it's a no-op (no settings write, no log line). When it does prune, it logs [cronGroupLabels] pruned N orphan keys (M remaining).
A soft-deleted project counts as missing — queries.listProjects() filters out is_deleted = 1, so the prune sees it as gone. This is intentional: undelete is rare enough that recovering a stale group label override would be more confusing than just letting the user re-rename.
Where the data lives
| Layer | File | Purpose |
|---|---|---|
| Type | src/shared/types.ts | AppSettings.cronGroupLabels: Record<string, string> |
| Schema | src/shared/ipc-schemas.ts | Zod updateSettingsSchema field — rejects empty values |
| Resolver | src/renderer/src/features/cron/cron-group-label.ts | useGroupLabelResolver(viewMode) + MAX_GROUP_LABEL_LEN |
| Header | src/renderer/src/features/cron/CronJobGroupHeader.tsx | Inline rename input + pencil affordance |
| Wiring | src/renderer/src/features/cron/CronDashboard.tsx | handleRenameGroup on the main view |
| Wiring | src/renderer/src/features/cron/CronJobsSidebar.tsx | handleRenameGroup on the cron sidebar |
| GC | src/main/services/cron-group-labels-service.ts | pruneOrphanCronGroupLabels() |
Tests
- tests/unit/features/cron/cron-job-group-header.test.tsx — 14 RTL tests covering frequency-mode parity, rename trigger render, input pre-fill, propagation isolation, Enter / Ctrl+Enter save, MAX_GROUP_LABEL_LEN clamp, empty / whitespace / equal-to-live → reset, ESC cancel, orphan group renameability.
- The resolver and prune service have their own unit tests — see the cron-group-label resolver test and the cron-group-labels-service test for the underlying behavior.
Related
How cron jobs are created and edited in the first place is on the create a cron job with AI page. What happens when one of them fails is on the cron failure alerts page, and the session Omniscio can start to repair a broken job automatically is on the cron self-healing page.
Last verified 2026-09-23