Skip to content

feat(work): scheduled tasks under Work mode#2134

Merged
charlescook-ph merged 1 commit into
project-workfrom
posthog-code/work-scheduled-tasks-preview
May 13, 2026
Merged

feat(work): scheduled tasks under Work mode#2134
charlescook-ph merged 1 commit into
project-workfrom
posthog-code/work-scheduled-tasks-preview

Conversation

@charlescook-ph
Copy link
Copy Markdown

Summary

Adds a Scheduled section to PostHog Work, on top of the existing TaskAutomation backend. Wires the sidebar's previously-static Automations entry into a real list + editor, with a non-engineer-friendly UX.

  • Natural-language schedule field: type "every Tuesday at 5pm", "daily at 9am", "weekdays at 9am", "15th of the month at 8am", or raw cron. Live parse preview shows what you typed → "Tuesdays at 5pm". Save disabled when unparseable. Quick-fill chips below for common patterns.
  • Data sources picker: chip multi-select pulling from the user's connected MCP servers via useMcpServers(), with brand icons. Selected sources are persisted as a [Sources: github, slack] prefix on the prompt (stopgap until the backend grows a first-class field).
  • Curated examples in the empty state — Weekly competitive analysis, Daily meeting briefing, Customer feedback digest, Monthly metric review, Weekly product update — each prefills name + prompt.
  • Last-run inline panel in the editor with "Open task" deep link that flips to Code mode and opens the spawned TaskRun.
  • Status badge covers Paused / Healthy / Failed / Running / Cancelled / Active.

Plugs into the existing workView state in navigationStore (adds scheduled-list and scheduled-edit, plus matching nav actions). Pure additions to WorkSidebarMenu (renames "Automations" → "Scheduled", wires its click) and WorkView (new routing branches).

Known stopgaps

  • repository: "posthog-work" sentinel is sent because the current TaskAutomation.repository field is required + non-blank. Backend ticket needed to make it nullable for Work-mode tasks.
  • MCP source choice is stuffed into the prompt as a [Sources: …] header. Works today (agent treats it as advisory). Backend ticket needed for a first-class mcp_sources: string[] field.

Test plan

  • In Work mode, click Scheduled in the sidebar — lands on an empty state with 5 clickable examples.
  • Click an example — editor opens with name + prompt prefilled.
  • Type "every Tuesday at 5pm" → see green "→ Tuesdays at 5pm". Type "blue chickens" → amber "couldn't understand" hint. Save disabled until parseable.
  • Quick-fill chips fill the field with valid NL.
  • Pick a couple of data sources — chips toggle, get persisted in the prompt header on save.
  • Save → row appears in the list with the right schedule label + next-run time.
  • Click row → editor reopens with everything (including selected sources) repopulated.
  • Run now → toast, new TaskRun spawned; Open task swaps to Code mode and opens it.
  • Delete → row gone after refetch.
  • Toggle back to Home in the sidebar — sample-projects splash still works.
  • Click any Skills sidebar item — skills flow still works (untouched).

Fills the empty Work-mode placeholder with a non-engineer-friendly UI on
top of the existing TaskAutomation backend. A scheduled task is a name,
a free-text prompt, and a schedule preset — the agent picks the right
skill at run time, no skill picker needed.

Highlights:
- Five schedule presets (hourly / daily / weekdays / Mondays / monthly)
  with computed next-run times shown per preset and per list row.
- Empty state seeded with four clickable example prompts (audit flags,
  inbox summary, web analytics digest, LLM cost review) that prefill
  the editor.
- "Open task" deep link from a scheduled task's last run — fetches via
  TaskService.openTask and swaps the app to Code mode.
- Sends repository="" plus the user's detected IANA timezone so creates
  succeed against the current backend schema.
- Status badge handles Paused / Healthy / Failed / Running / Cancelled
  / Active and renders inline last-run error callouts.

Adds five PostHogAPIClient methods (list/create/update/delete + run),
a Zustand store for view + pending-prompt state, schedule + timezone
utilities, React Query hooks, and the components.

Generated-By: PostHog Code
Task-Id: 8370b362-c3fd-4abe-8f0e-9dc7ecd74257
@charlescook-ph charlescook-ph force-pushed the posthog-code/work-scheduled-tasks-preview branch from fc06744 to 3c3628e Compare May 13, 2026 21:07
@charlescook-ph charlescook-ph merged commit cf2c79f into project-work May 13, 2026
8 checks passed
@charlescook-ph charlescook-ph deleted the posthog-code/work-scheduled-tasks-preview branch May 13, 2026 21:08
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Comments Outside Diff (1)

  1. apps/code/src/renderer/features/work/components/ScheduledTaskEditor.tsx, line 475-479 (link)

    P1 User edits lost on every polling cycle

    existing is in the dependency array alongside existing?.id. Because useScheduledTasks refetches every 30 seconds, polling updates (e.g. last_run_at or last_run_status changing) produce a new object reference with the same id, causing the effect to fire and reset the draft — clobbering whatever the user has typed. The comment on the next line explicitly states only the id should trigger a reset, but the deps don't reflect that intent. Fix: use only [existing?.id] (or [editingId]).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/code/src/renderer/features/work/components/ScheduledTaskEditor.tsx
    Line: 475-479
    
    Comment:
    **User edits lost on every polling cycle**
    
    `existing` is in the dependency array alongside `existing?.id`. Because `useScheduledTasks` refetches every 30 seconds, polling updates (e.g. `last_run_at` or `last_run_status` changing) produce a new object reference with the same `id`, causing the effect to fire and reset the draft — clobbering whatever the user has typed. The comment on the next line explicitly states only the `id` should trigger a reset, but the deps don't reflect that intent. Fix: use only `[existing?.id]` (or `[editingId]`).
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/code/src/renderer/features/work/components/ScheduledTaskEditor.tsx:475-479
**User edits lost on every polling cycle**

`existing` is in the dependency array alongside `existing?.id`. Because `useScheduledTasks` refetches every 30 seconds, polling updates (e.g. `last_run_at` or `last_run_status` changing) produce a new object reference with the same `id`, causing the effect to fire and reset the draft — clobbering whatever the user has typed. The comment on the next line explicitly states only the `id` should trigger a reset, but the deps don't reflect that intent. Fix: use only `[existing?.id]` (or `[editingId]`).

```suggestion
  // Sync the draft when the editor is pointed at a different existing automation.
  useEffect(() => {
    if (existing) setDraft(toDraft(existing));
    // The id is what should trigger a reset — other field changes from polling
    // shouldn't clobber user edits.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [existing?.id]);
```

### Issue 2 of 3
apps/code/src/renderer/api/posthogClient.ts:183-190
**Type-unsafe double cast on the run endpoint body**

`{} as unknown as Schemas.TaskAutomation` is a double cast that bypasses TypeScript entirely. The `/run/` endpoint almost certainly does not require a `TaskAutomation` body; if it takes no body at all the `body` key can be omitted, or if the generated client requires some body shape it should match the actual schema type.

```suggestion
    const data = await this.api.post(
      `/api/projects/{project_id}/task_automations/{id}/run/`,
      {
        path: { project_id: teamId.toString(), id: automationId },
      },
    );
```

### Issue 3 of 3
apps/code/src/renderer/features/work/components/WorkHome.tsx:1-54
**Dead-code file — never rendered**

`WorkHome.tsx` is a new file added in this PR, but `WorkView.tsx` still imports `beachHog`, `WorkHomePrompt`, and `WorkSampleProjects` directly and renders the home content inline — its default branch is unchanged and never mounts `<WorkHome />`. This violates simplicity rule 4 ("has no superfluous parts"). Either update `WorkView`'s default branch to `return <WorkHome />;` and remove the duplicated inline JSX, or delete this file until the refactor is intentional.

Reviews (1): Last reviewed commit: "feat(work): add scheduled tasks UI to Wo..." | Re-trigger Greptile

Comment on lines +1 to +54
import { Box, Flex, Text } from "@radix-ui/themes";
import beachHog from "@renderer/assets/images/hedgehogs/beach-hog.png";
import { WorkHomePrompt } from "./WorkHomePrompt";
import { WorkSampleProjects } from "./WorkSampleProjects";

export function WorkHome() {
return (
<Box className="scrollbar-overlay-y h-full w-full overflow-y-auto">
<Flex
direction="column"
align="center"
gap="6"
className="mx-auto w-full max-w-[680px] px-6 pt-16 pb-12"
>
<Flex
direction="column"
align="center"
gap="3"
className="work-enter work-enter-1"
>
<img
src={beachHog}
alt=""
className="h-28 w-auto select-none"
draggable={false}
/>
<Box className="text-center">
<Text
as="div"
weight="medium"
className="text-(--gray-12) text-[22px]"
>
Hello normie, what can I do for you today?
</Text>
</Box>
</Flex>

<Box className="work-enter work-enter-2 w-full">
<WorkHomePrompt />
</Box>

<Box className="work-enter work-enter-3 w-full">
<Text
as="div"
className="mb-2 text-center text-(--gray-10) text-[11px] uppercase tracking-wide"
>
Or if you're used to outsourcing your brain to Claude...
</Text>
<WorkSampleProjects />
</Box>
</Flex>
</Box>
);
}
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.

P2 Dead-code file — never rendered

WorkHome.tsx is a new file added in this PR, but WorkView.tsx still imports beachHog, WorkHomePrompt, and WorkSampleProjects directly and renders the home content inline — its default branch is unchanged and never mounts <WorkHome />. This violates simplicity rule 4 ("has no superfluous parts"). Either update WorkView's default branch to return <WorkHome />; and remove the duplicated inline JSX, or delete this file until the refactor is intentional.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/work/components/WorkHome.tsx
Line: 1-54

Comment:
**Dead-code file — never rendered**

`WorkHome.tsx` is a new file added in this PR, but `WorkView.tsx` still imports `beachHog`, `WorkHomePrompt`, and `WorkSampleProjects` directly and renders the home content inline — its default branch is unchanged and never mounts `<WorkHome />`. This violates simplicity rule 4 ("has no superfluous parts"). Either update `WorkView`'s default branch to `return <WorkHome />;` and remove the duplicated inline JSX, or delete this file until the refactor is intentional.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant