Skip to content

feat(scheduled-tasks): replace the schedules table with calendar views#4979

Open
emir-karabeg wants to merge 4 commits into
stagingfrom
feat/calendar-tasks
Open

feat(scheduled-tasks): replace the schedules table with calendar views#4979
emir-karabeg wants to merge 4 commits into
stagingfrom
feat/calendar-tasks

Conversation

@emir-karabeg

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the scheduled-tasks table with a calendar: month, week, and day views with a Today control, scope dropdown, chevron navigation, and a Google-Calendar-style current-time indicator on today's column. Week view is the default and Today scroll-centers the current time
  • Clicking a day or hour slot opens a create-task ChipModal (prompt textarea + chip date/time pickers, full width). Submit is a typed stub for now — the create contract requires a non-empty cronExpression, so one-time persistence lands in a follow-up. The grid components already accept eventsByDay/eventsByHour props so rendering real schedules as events only touches the container later
  • Calendar math is pure and unit-tested (calendar-grid.ts, schedule-events.ts, 12 tests); view state lives in a useCalendar hook, components follow the one-folder-per-component convention with barrels
  • Fix emcn DropdownMenu inside dialogs: a non-modal menu portals outside the dialog's react-remove-scroll subtree so its content can't be wheel-scrolled. ModalContent now marks its subtree via an InsideModal context and the menu root upgrades itself to modal inside dialogs; page-level menus keep their consumer-chosen modality. (Depends on the singleton dedupe shipped in fix(deps): dedupe radix focus-scope/dismissable-layer so in-modal dropdowns open #4977)
  • Resource shell simplifications: Resource.Table drops internal sorting and empty-state handling, the root becomes the overlay positioning context, toasts run independent timers with chip-aligned typography, breadcrumbs use the canonical loading placeholder
  • Rename the "Mothership" agent to "Sim" and the chat surface to "Chat" across landing copy, constitution, block metadata, and API error messages

Type of Change

  • New feature

Testing

  • bunx vitest run on the scheduled-tasks utils — 12/12 passing (grid derivation, anchor advancement, labels, event bucketing)
  • bunx tsc --noEmit clean; biome clean on all touched files
  • Manual: month/week/day navigation, Today centering, slot click → pre-filled modal, time dropdown opens and scrolls inside the modal, dark mode tokens

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…dcrumbs

- Resource.Table: remove internal sorting (defaultSort/sortValues) and the
  emptyMessage state — rows render in the order given, chrome always paints
- Resource: root is now the positioning context for overlays; consumers
  (files, tables, knowledge, document) wrap detail views in <Resource>
  instead of hand-rolled divs
- ResourceHeader: root titles no longer truncate during initial layout;
  LocationFocusVeil gates the portal on mount to fix a hydration mismatch
- Toasts: drop the StackDismiss ring and stack countdown — each toast runs
  its own timer; remove the Mod+E clear-notifications command; align toast
  typography and icons with chip chrome
- Breadcrumbs: use the canonical '…' placeholder while names load
- incident.io: fix display name and catalog slug (with redirect)
- Add dev:capped / dev:full:capped scripts with a 4GB heap cap
Add month/time calendar views for scheduled tasks with toolbar, event
chips, and a create-task modal, backed by calendar-grid and
schedule-events utils (with tests) and a use-calendar hook. Replace the
old schedule-modal/context-menu flow.

Rename the "Mothership" agent to "Sim" and the chat surface to "Chat"
across landing copy, constitution, block metadata, API error messages,
and copilot/data-drain internals. Drop unused workspace route layouts.
A non-modal DropdownMenu portals outside an open dialog's
react-remove-scroll subtree, so its content cannot be wheel-scrolled
(e.g. the time picker in the scheduled-task create modal). ModalContent
now marks its subtree via an InsideModal context, and the emcn
DropdownMenu root upgrades itself to modal inside dialogs so it mounts
its own scroll lock and focus scope; page-level menus keep their
consumer-chosen modality.

Also stretch the create-task modal's date/time chip controls to full
width and drop the dead EDGE_GUTTER constant left behind by the
equal-tracks calendar layout.
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 11, 2026 9:15pm

Request Review

@emir-karabeg

Copy link
Copy Markdown
Collaborator Author

@greptile run

@cursor

cursor Bot commented Jun 11, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Scheduled tasks loses list-based edit/pause/delete until calendar persistence and event injection ship, and Resource.Table no longer handles loading/empty/sort internally—callers must behave correctly on files, knowledge, logs, and tables pages.

Overview
Scheduled tasks drops the searchable table, filters, and full ScheduleModal CRUD (edit, pause, resume, delete) in favor of a month / week / day calendar with toolbar navigation, a live “now” line, and slot clicks that open a lightweight CreateTaskModal (submit is a stub; persistence and showing existing schedules on the grid are left for follow-up, though schedule-events helpers and eventsByDay/eventsByHour props are in place).

Resource shell changes: Resource.Table no longer sorts rows internally or shows built-in loading/empty states—pages keep headers visible and rely on Resource.Options for sort. Several workspace routes drop extra layout wrappers, use Resource for file/detail shells, and standardize breadcrumb while loading. Resource header fixes title truncation on load and hydration for the location-focus veil.

emcn: DropdownMenu forces modal behavior inside ModalContent (via InsideModalProvider) so menus scroll inside dialogs.

Product copy renames Mothership/Copilot to Sim (agent) and Chat (surface) across landing, docs, settings, blocks, APIs, and constitution; minor label fixes (e.g. incident.io).

Removes the Mod+E “clear notifications” global command from workspace permissions.

Reviewed by Cursor Bugbot for commit cdb3ae4. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cdb3ae4. Configure here.

if (onSubmit) onSubmit(draft)
else logger.info('Scheduled task draft captured (not persisted this phase)', draft)
close()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Modal form not reset on close

Medium Severity

Closing the create modal does not clear local prompt, launchDate, or launchTime. Reopening from the header keeps the same React key (none), so cancelled or submitted drafts reappear in the form.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdb3ae4. Configure here.


const next = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, 1)), [scope])
const prev = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, -1)), [scope])
const goToday = useCallback(() => setAnchor(new Date()), [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Today highlight uses stale date

Medium Severity

today is fixed at hook mount while goToday sets anchor to the current time. After midnight or a long session, Today navigation and isToday styling can disagree because the grid still compares days against the old today value.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdb3ae4. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the scheduled-tasks table with a month/week/day calendar view, adds a new CreateTaskModal opened from any slot, fixes DropdownMenu scroll-lock inside dialogs via a new InsideModal context, simplifies the Resource shell (drops internal sorting/empty-state), and renames "Mothership" to "Sim" across landing copy and API routes.

  • Calendar feature: Pure calendar-grid.ts / schedule-events.ts utilities (12 unit tests), a useCalendar hook, and three view components (MonthGrid, TimeGrid, CalendarToolbar) with a CurrentTimeIndicator that ticks every minute; event injection is wired for later via eventsByDay/eventsByHour props.
  • Modal/dropdown fix: ModalContent now wraps its subtree in InsideModalProvider; DropdownMenu reads that context and forces modal=true inside dialogs, restoring wheel-scroll on the time picker inside CreateTaskModal.
  • CreateTaskModal stub: The "Schedule" button currently closes the modal and logs a draft to the console with no persistence or user feedback, pending a follow-up to wire the create API.

Confidence Score: 3/5

The calendar UI is well-structured and the emcn dialog/dropdown fix is sound, but two issues need attention before this reaches users: the Schedule button silently drops the user input with no feedback, and the frozen today reference means the time indicator and today-column highlight become incorrect after midnight without a page reload.

The calendar math is clean and fully tested. The InsideModal/DropdownMenu fix is correct. However, the CreateTaskModal submit path closes without any user feedback — a user who fills in the prompt and clicks Schedule gets no toast, no error, no confirmation, and no task is created. Additionally, useCalendar freezes today at mount: after midnight the isToday column marking and the CurrentTimeIndicator placement diverge from actual current time, persisting until the component unmounts.

use-calendar.ts (frozen today) and create-task-modal.tsx (silent no-op submit) need attention before merging to a user-facing environment.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/hooks/use-calendar.ts New hook owning calendar view state; today is frozen at mount via useMemo, causing stale isToday marks and a misplaced time-indicator column after midnight
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/create-task-modal/create-task-modal.tsx New modal for creating tasks; submit handler is a no-op stub that closes silently with no user feedback, which will mislead users who click "Schedule"
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/schedule-calendar/schedule-calendar.tsx Calendar container wiring scope/anchor to grid derivation and scroll centering; scroll behavior is correct but instant (no smooth option)
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/calendar-grid.ts Pure calendar math utilities — grid building, label formatting, time offset; clock-free and well-tested
apps/sim/components/emcn/components/dropdown-menu/dropdown-menu.tsx DropdownMenu now reads InsideModal context and forces modal=true inside dialogs, fixing the scroll-lock/focus-trap issue with nested dropdowns
apps/sim/components/emcn/components/modal/modal-context.ts New InsideModalContext and hook providing the signal consumed by DropdownMenu to upgrade modality inside dialogs
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/schedule-calendar/components/time-grid/time-grid.tsx Week/day time grid with CurrentTimeIndicator; the indicator renders on the column marked isToday (derived from frozen today), which can diverge from the live ticking time after midnight
apps/sim/components/emcn/components/toast/toast.tsx Toast simplification: removes stack-dismiss countdown and VARIANT_ICON_COLOR, aligns icon/text to chip typography tokens; restructures description outside the header row
apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx Resource shell simplified: drops internal sorting, sortValues, and empty-state handling from Table; root becomes overlay positioning context; import paths upgraded to canonical aliases

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    ST[ScheduledTasks page] --> UC[useCalendar hook]
    UC --> SC[ScheduleCalendar]
    SC --> CT[CalendarToolbar]
    SC --> MG[MonthGrid]
    SC --> TG[TimeGrid]
    TG --> CTI[CurrentTimeIndicator]
    ST --> CTM[CreateTaskModal]
    CTM --> CD[ChipDropdown time picker]
    UC -- selectSlot --> CTM
    UC -- openCreate --> CTM
Loading

Reviews (1): Last reviewed commit: "fix(emcn): force dropdown menus modal in..." | Re-trigger Greptile

Comment on lines +64 to +73
const handleSubmit = () => {
const draft: CreateTaskDraft = {
prompt: prompt.trim(),
launchDate,
launchTime,
timezone: DEFAULT_TIMEZONE,
}
if (onSubmit) onSubmit(draft)
else logger.info('Scheduled task draft captured (not persisted this phase)', draft)
close()

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.

P1 Stub submit silently closes with no user feedback

When onSubmit is not supplied (the current call site passes none), handleSubmit logs to the console and closes the modal. A user who fills in the prompt and clicks "Schedule" sees the modal disappear without any toast, error, or "coming soon" indicator — they will believe a task was created. Even as a deliberate stub, showing a brief toast.info("Scheduling support coming soon") or keeping the button disabled until the feature is wired would avoid this false-success UX.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2e52a5d — the stub path now shows toast.info('Scheduling is not available yet — this task was not created') so there's no false-success UX while persistence is pending the one-time-launch contract work.

Comment on lines +39 to +47
const today = useMemo(() => new Date(), [])
const [scope, setScope] = useState<CalendarScope>('week')
const [anchor, setAnchor] = useState<Date>(() => new Date())
const [selectedSlot, setSelectedSlot] = useState<CalendarSlot | null>(null)
const [isCreateOpen, setIsCreateOpen] = useState(false)

const next = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, 1)), [scope])
const prev = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, -1)), [scope])
const goToday = useCallback(() => setAnchor(new Date()), [])

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.

P1 Frozen today causes stale isToday marks after midnight

today is captured once with useMemo(() => new Date(), []) and never refreshed. The grid's isToday flags and the today-column indicator in TimeGrid (which only renders <CurrentTimeIndicator /> on day.isToday) are both derived from this frozen value. After midnight, the indicator stays on the previous day's column even though CurrentTimeIndicator internally ticks to the new time. Similarly, goToday sets the anchor to new Date() (actual today) but the today prop fed to buildCalendarGrid remains yesterday, so the wrong column gets highlighted. Adding a useEffect that refreshes today at midnight — or simply not memoizing it — would fix the mismatch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2e52a5dtoday is now state refreshed by a minute-interval poll guarded by isSameDay, so it only re-renders on actual day rollover. Chose the poll over a one-shot timeout aimed at midnight because intervals survive device sleep/clock changes.

Comment on lines +79 to +80
const target = headerHeight + timeToOffset(new Date()) - region.clientHeight / 2
region.scrollTo({ top: Math.max(0, target) })

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 The scrollTo call has no behavior option, so the jump is instant on scope change and on Today clicks. Adding behavior: 'smooth' gives a nicer orientation cue when the view shifts.

Suggested change
const target = headerHeight + timeToOffset(new Date()) - region.clientHeight / 2
region.scrollTo({ top: Math.max(0, target) })
const target = headerHeight + timeToOffset(new Date()) - region.clientHeight / 2
region.scrollTo({ top: Math.max(0, target), behavior: 'smooth' })

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Adopted with a tweak in 2e52a5d: smooth scrolling applies only to Today presses (an orientation cue), while mount and scope switches keep instant positioning — animating initial placement reads as a glitch rather than a cue. Style note for next time: we prefer behavior: 'smooth' only for user-initiated re-orientation, never for initial positioning.

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.

That's a clean distinction — smooth only where it communicates intent, instant everywhere else. Rule saved.

View rule

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the scheduled-tasks table with a three-scope calendar (month/week/day), adds a ChipModal-based create-task form seeded from the clicked slot, simplifies the Resource shell (drops internal sort and empty-state handling from Resource.Table), fixes DropdownMenu scroll inside Radix dialogs via a new InsideModalContext, and renames "Mothership" to "Sim" across landing copy and API surfaces.

  • Calendar views: pure calendar-grid.ts/schedule-events.ts utilities with 12 unit tests power month/week/day grids; view state lives in useCalendar; a Google Calendar-style current-time indicator ticks every minute; event injection slots (eventsByDay/eventsByHour) are wired but intentionally empty until persistence ships.
  • Modal dropdown fix: DropdownMenu now reads InsideModalContext (set by ModalContent) and forces modal={true} inside dialogs, resolving the react-remove-scroll wheel-scroll incompatibility without affecting page-level menus.
  • Resource shell: Resource.Table drops emptyMessage, isLoading, defaultSort, and sortValues; the root gains position: relative as the overlay positioning context; consumers updated accordingly.

Confidence Score: 4/5

Safe to merge for the calendar UI and modal-dropdown fix; the today reference freezes at mount which breaks the today-highlight after midnight, and search/filter empty-state messages were removed without replacement.

The today value in useCalendar is computed once via useMemo(() => new Date(), []) and never refreshed. After midnight, goToday correctly advances anchor but today stays the old day, so buildCalendarGrid marks yesterday as isToday while the actual current day renders with no highlight. Separately, emptyMessage was removed from Resource.Table and consumers dropped the variable without adding replacement rendering, so users searching or filtering to zero results see a silent empty table.

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/hooks/use-calendar.ts (stale today), apps/sim/app/workspace/[workspaceId]/files/files.tsx and apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx (missing empty-state feedback)

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/hooks/use-calendar.ts New hook for calendar view state; contains a stale today reference (frozen at mount via empty-dep useMemo) that will show the wrong day highlighted as "today" after midnight.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/calendar-grid.ts Pure calendar grid derivation utilities (build/advance/format); well-structured with no side effects — fully deterministic and unit-tested.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/schedule-events.ts Event adaptation and bucketing utilities; correct null/NaN guards on nextRunAt, consistent local-time bucketing aligned with the time grid display.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/scheduled-tasks.tsx Calendar page container; correctly wires useCalendar state to ScheduleCalendar and CreateTaskModal, slotKey re-seeds the modal on every new slot click.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/schedule-calendar/schedule-calendar.tsx Calendar body managing scroll and view dispatch; scroll-to-now logic may get a stale headerHeight on the first scope switch before the time-grid DOM is fully laid out.
apps/sim/components/emcn/components/dropdown-menu/dropdown-menu.tsx DropdownMenu now reads InsideModalContext and forces modal={true} inside Radix dialogs, fixing the wheel-scroll issue; page-level menus keep their consumer-set modality.
apps/sim/components/emcn/components/modal/modal-context.ts New InsideModalContext and useInsideModal hook; minimal, correctly implemented with a false default so non-modal trees are unaffected.
apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx Resource shell simplified: drops internal sort, emptyMessage, and isLoading from ResourceTable, making the root the overlay positioning context. Consumers that relied on emptyMessage for search/filter feedback now show silent empty tables.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/create-task-modal/create-task-modal.tsx New create-task modal using ChipModal; correctly stubs persistence (logs only), seeds date/time from the clicked slot, and disables Submit until prompt is non-empty.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/schedule-calendar/components/time-grid/time-grid.tsx Time grid for week/day scopes; CurrentTimeIndicator correctly defers to client-side only (avoiding hydration mismatch) and ticks every minute independently of the stale today reference.

Sequence Diagram

sequenceDiagram
    participant User
    participant ScheduledTasks
    participant useCalendar
    participant ScheduleCalendar
    participant CreateTaskModal

    User->>ScheduledTasks: mount
    ScheduledTasks->>useCalendar: "init (scope=week, anchor=now, today=now frozen)"
    useCalendar-->>ScheduledTasks: state
    ScheduledTasks->>ScheduleCalendar: render(scope, anchor, today)

    User->>ScheduleCalendar: click time slot
    ScheduleCalendar->>ScheduledTasks: onSelectSlot(date, time)
    ScheduledTasks->>useCalendar: selectSlot(date, time)
    useCalendar-->>ScheduledTasks: "selectedSlot set, isCreateOpen=true"
    ScheduledTasks->>CreateTaskModal: "open=true, slot"
    User->>CreateTaskModal: fill prompt, click Schedule
    CreateTaskModal->>CreateTaskModal: logger.info stub, no persistence
    CreateTaskModal-->>ScheduledTasks: onOpenChange(false)
    ScheduledTasks->>useCalendar: closeCreate()

    User->>ScheduleCalendar: click Today
    ScheduleCalendar->>ScheduledTasks: onToday()
    ScheduledTasks->>useCalendar: "goToday sets anchor=new Date()"
    ScheduleCalendar->>ScheduleCalendar: scrollSignal++ scrollTo(timeToOffset(now))
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/files/files.tsx, line 1890-1904 (link)

    P2 Empty-state feedback lost after search/filter

    emptyMessage was removed from ResourceTable without a replacement in consumer code. Before this PR, searching for a file that doesn't exist (e.g. a typo in the search box) or applying a filter that matches nothing would display a centred message ("No files match…" / "Nothing matches your filter"). Now rows = [] produces a silent empty table body — users get no indication of why the list is blank. The same regression appears in the Knowledge Base (knowledge/[id]/base.tsx).

Reviews (2): Last reviewed commit: "fix(emcn): force dropdown menus modal in..." | Re-trigger Greptile

* mutations are event-driven; there are no effects. Opens on the `week` scope.
*/
export function useCalendar(): UseCalendarReturn {
const today = useMemo(() => new Date(), [])

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.

P1 Stale today reference after midnight

today is memoized with an empty deps array, so it freezes the date at component mount and is never refreshed. If the page stays open past midnight and the user clicks "Today" (which correctly advances anchor to the new date via setAnchor(new Date())), today remains the previous day — so buildCalendarGrid marks the OLD day as isToday while the actual current day renders unhighlighted. The mismatch is visible: the "today" highlight sits on yesterday while the current-time indicator line correctly tracks the real time.

A simple fix is to derive today from the current clock on each render of the dependent computation (pass new Date() directly into buildCalendarGrid instead of memoizing it), or to refresh it with a daily setInterval.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Already addressed in 2e52a5d (this comment was reviewed against the prior commit) — today is state refreshed by an isSameDay-guarded minute poll, and goToday now agrees with the highlight after midnight.

Comment on lines +79 to +92
const target = headerHeight + timeToOffset(new Date()) - region.clientHeight / 2
region.scrollTo({ top: Math.max(0, target) })
}, [scope, scrollSignal])

return (
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden'>
<CalendarToolbar
scope={scope}
label={label}
onPrev={onPrev}
onNext={onNext}
onToday={handleToday}
onScopeChange={onScopeChange}
/>

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 Scroll-to-now fires before the time-grid DOM is painted on scope switch

useEffect is gated on [scope, scrollSignal]. When the user switches from month to week or day, React re-renders synchronously and schedules the effect — but because the outgoing branch's DOM is still in tree until the paint, region.querySelector('[data-time-grid-header]') can briefly find zero nodes (the month grid has no such element) or a stale layout, so headerHeight evaluates to 0 and the scroll target is computed from timeToOffset alone. On slower devices the first scope-switch may land the user at slightly the wrong scroll position. Wrapping the non-month branch in requestAnimationFrame would guarantee layout is settled before reading heights.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Declining this one — the mechanism described doesn't occur. React runs passive effects after the commit phase, so when the effect fires on a scope switch the month grid is already unmounted and [data-time-grid-header] is in the DOM; getBoundingClientRect() forces a synchronous fresh layout, so headerHeight can't read 0 or stale. Wrapping in requestAnimationFrame would only delay the scroll by a frame and make the unpositioned grid briefly visible.

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.

You're right — I had the effect timing wrong. useEffect fires after paint, not just after commit, so by the time the scroll runs on a scope switch the new branch is already in the DOM and getBoundingClientRect() reflects the settled layout. The headerHeight === 0 scenario I described can't happen for the reason you stated. Withdrawing the comment.

…ck, smooth Today scroll

- useCalendar: today was frozen at mount, so after midnight the isToday
  column highlight and the current-time indicator stayed on the previous
  day. today is now state refreshed by a sleep-resilient minute poll
  that only re-renders when the calendar day actually changes
- CreateTaskModal: the stub submit closed silently, reading as false
  success; it now shows an info toast that the task was not created
- ScheduleCalendar: Today presses scroll smoothly as an orientation
  cue; mount and scope switches keep instant positioning
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