Skip to content

refactor(calendar): de-emphasize agent UX in event modal - #1237

Merged
2witstudios merged 4 commits into
masterfrom
pu/cal-parity
May 4, 2026
Merged

2witstudios merged 4 commits into
masterfrom
pu/cal-parity

Conversation

@2witstudios

@2witstudios 2witstudios commented May 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Replace the always-visible 'Configure agent trigger' button (and its separate dialog) with an inline collapsed disclosure inside EventModal. Header reads Off or <agent name> at start, so the modal reads as a normal calendar by default and surfaces which agent is attached when one is, without a second-dialog hop.
  • Move Linked page under a collapsed Advanced section so casual users see only normal calendar fields (Title / Date / Time / Location / Color / Description). Add a small Zap indicator on event chips in MonthView/WeekView/DayView/AgendaView/MobileDayAgenda whenever hasAgentTrigger is true (using the same aria-hidden icon + sr-only text pattern as TaskListView's amber Trigger badge).
  • Wire agentTrigger (three-state: undefined no-op, null remove, object upsert) through useCalendarData and CalendarView.handleEventSave to the existing POST/PATCH endpoints. Recurring events render an inline "can't have agent triggers" note instead of the form. Personal events get no agent affordance at all.
  • Defensive: a recurring event with a stale trigger row (shouldn't exist, API rejects them) keeps agentEnabled=false on hydration AND skips sending agentTrigger=null on save, so unrelated edits don't silently delete the broken row.
  • A11y polish: DialogDescription added to satisfy Radix's aria-describedby contract; the agent-trigger Switch is now properly associated with its Label via id/htmlFor.
  • Retire EventAgentTriggerDialog + its test in favor of EventModal.test.tsx (10 tests covering: collapsed default, hidden for personal events, prefilled when editing, recurring-disabled note + stale-row save defense, Advanced collapse, save-with-trigger payload, save-with-toggle-off → null, personal-save omits agentTrigger, and SWR pause across remote refetch).

Why

PR #1232 wired up agent triggers but the resulting UX over-rotated toward agent scheduling: every saved drive event got a full-width "Configure agent trigger" button, "Linked page" read as agent context, and the agent picker was hidden behind two clicks. The user complaint was "is it always an agent event or how do you select which agent? this should still function as a normal calendar."

Test plan

  • pnpm --filter @pagespace/db build && pnpm --filter @pagespace/lib build (refresh stale dist; the calendar/workflow typecheck noise was caused by dist/schema/{workflow-runs,task-triggers}.{js,d.ts} never being compiled and calendar-triggers.d.ts describing the pre-workflowId schema)
  • pnpm --filter web typecheck clean
  • pnpm --filter web lint clean (one pre-existing warning in unrelated file)
  • pnpm --filter web test src/components/calendar — 26 tests pass (16 existing + 10 new EventModal)
  • pnpm --filter web test src/lib/workflows/__tests__/calendar-trigger-helpers.test.ts src/app/api/calendar — 43 tests pass
  • pnpm --filter web test src/lib/ai/tools/__tests__/calendar-write-tools.test.ts — 58 AI-tool tests pass
  • Personal calendar → create event → no agent affordance, no Linked page outside Advanced
  • Drive calendar → create event → expand Agent trigger → pick agent + prompt → save → POST sends agentTrigger, event saves with trigger attached
  • Drive calendar → edit event with existing trigger → header reads <agent name> at start; expand prefills section
  • Drive calendar → edit event → toggle disclosure to Off + save → PATCH sends agentTrigger: null, trigger removed
  • Recurring drive event → disclosure shows disabled note instead of form
  • Month/Week/Day/Agenda views → events with hasAgentTrigger=true show small Zap icon

🤖 Generated with Claude Code

Replaces the always-visible 'Configure agent trigger' button (and its separate dialog) with an inline collapsed disclosure inside EventModal. Header reads 'Off' or '<agent name> at start' so the modal reads as a normal calendar by default and surfaces which agent is attached when one is, without a second-dialog hop.

Moves 'Linked page' under a collapsed Advanced section so casual users see only normal calendar fields. Adds a small Zap indicator on event chips in Month/Week/Day/Agenda/MobileDayAgenda whenever hasAgentTrigger is true, matching the task list's amber Trigger badge.

Wires agentTrigger (three-state: undefined no-op, null remove, object upsert) through useCalendarData and CalendarView.handleEventSave to the existing POST/PATCH endpoints. Recurring events render an inline 'can't have agent triggers' note instead of the form. Personal events get no agent affordance at all.

Retires EventAgentTriggerDialog + its test in favor of EventModal.test.tsx covering the inline disclosure, Advanced collapse, recurring guard, and SWR pause across remote refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61cdc829-b407-4f8a-be1d-e53de1b7ce33

📥 Commits

Reviewing files that changed from the base of the PR and between 8f22a1a and b6aa131.

📒 Files selected for processing (7)
  • apps/web/src/components/calendar/AgendaView.tsx
  • apps/web/src/components/calendar/DayView.tsx
  • apps/web/src/components/calendar/EventModal.tsx
  • apps/web/src/components/calendar/MobileDayAgenda.tsx
  • apps/web/src/components/calendar/MonthView.tsx
  • apps/web/src/components/calendar/WeekView.tsx
  • apps/web/src/components/calendar/__tests__/EventModal.test.tsx
📝 Walkthrough

Walkthrough

This PR replaces a dedicated agent-trigger dialog with inline configuration within the event modal, removes EventAgentTriggerDialog, adds visual Zap icon indicators across all calendar views to show when events have agent triggers configured, updates type signatures in useCalendarData to support agent trigger payloads, and includes comprehensive tests for the new modal behavior.

Changes

Agent Trigger Configuration Migration

Layer / File(s) Summary
Data Shape
src/components/calendar/useCalendarData.ts
createEvent and updateEvent callbacks now accept optional agentTrigger field with shape: { agentPageId: string; prompt?: string; instructionPageId: string | null; contextPageIds: string[] }.
Core Feature
src/components/calendar/EventModal.tsx
Inline agent-trigger configuration replaces the prior separate dialog. Adds SWR-backed fetching of existing triggers and available agents, local state management for agent selection, validation for recurring events, and a collapsible "Agent trigger" disclosure section. Moves "Linked page" UI into a collapsible "Advanced" section for drive contexts. Computes and passes agentTrigger payload on save.
Save Integration
src/components/calendar/CalendarView.tsx
handleEventSave now destructures agentTrigger from the modal's eventData and conditionally passes it to createEvent, converting null to undefined to omit it from the POST body when not configured.
Calendar View Indicators
src/components/calendar/DayView.tsx, src/components/calendar/WeekView.tsx, src/components/calendar/MonthView.tsx, src/components/calendar/AgendaView.tsx, src/components/calendar/MobileDayAgenda.tsx
All calendar view files import Zap from lucide-react and conditionally render a small amber lightning icon next to event titles/pills when event.hasAgentTrigger is true.
Component Context
src/components/agent-triggers/AgentTriggerSection.tsx
JSDoc comment updated to reflect use in the EventModal's inline agent disclosure instead of a separate dialog.
Tests & Migration
src/components/calendar/__tests__/EventAgentTriggerDialog.test.tsx, src/components/calendar/__tests__/EventModal.test.tsx
Old dialog test file removed. New comprehensive EventModal test suite added with 336 lines covering: personal vs. drive context rendering, collapsed/expanded states, recurring event restrictions, agent name resolution, save payload shapes (agentTrigger as object, null, or undefined), and prompt persistence across SWR refetch.

Sequence Diagram

sequenceDiagram
    participant User
    participant EventModal
    participant SWR as SWR Fetchers
    participant API
    participant CalendarView
    participant CalendarView2 as useCalendarData

    User->>EventModal: Opens modal to edit/create event
    EventModal->>SWR: Request existing trigger (if updating)
    EventModal->>SWR: Request available agents
    SWR->>API: Fetch /triggers & /agents
    API-->>SWR: Return trigger data & agent list
    SWR-->>EventModal: Hydrate agent state from loaded trigger
    
    User->>EventModal: Configure agent (select, edit prompt, toggle on/off)
    Note over EventModal: Local agent state updated
    
    User->>EventModal: Click Save
    EventModal->>EventModal: Validate (recurring check, required fields)
    EventModal->>EventModal: Compute agentTrigger payload
    EventModal->>CalendarView: onSave(eventData with agentTrigger)
    
    CalendarView->>CalendarView: handleEventSave extracts agentTrigger
    alt Create Event
        CalendarView->>CalendarView2: createEvent(eventData with agentTrigger)
    else Update Event
        CalendarView->>CalendarView2: updateEvent(eventId, eventData with agentTrigger)
    end
    
    CalendarView2->>API: POST/PUT event (includes agentTrigger)
    API-->>CalendarView2: Event saved with trigger
    
    EventModal-->>User: Modal closes
    CalendarView->>User: Calendar view updates with new/modified event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • 2witstudios/PageSpace#1147: Modifies calendar agent-trigger surface and data flow (agentTrigger payload, hasAgentTrigger annotations, calendar filtering/consumption).
  • 2witstudios/PageSpace#862: Introduces backend calendarTriggers schema, cron executor, and AI tools; this PR's frontend UI and payload handling directly integrate with that backend contract.
  • 2witstudios/PageSpace#334: Adds foundational calendar feature; this PR extends the same calendar components (EventModal, useCalendarData, view files) to add agent-trigger configuration.

Poem

⚡ Triggers now live where modals dwellǃ
No dialog dance, just inline spell.
Zap icons shine on every view,
Agent config—fresh and true.
One cohesive flow: save and tell! 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% 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
Title check ✅ Passed The PR title 'refactor(calendar): de-emphasize agent UX in event modal' clearly and accurately summarizes the main objective of this changeset: refactoring the calendar component to reduce emphasis on agent trigger configuration by moving it from a prominent separate dialog to a collapsed inline disclosure within EventModal.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/cal-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Adds a sr-only DialogDescription to EventModal so the Radix Dialog has an aria-describedby (fixes the missing-Description warning that was emitted on every open).

Wires the agent-trigger Switch to its Label via id/htmlFor so the toggle has an accessible name.

Defensively keeps agentEnabled=false for recurring events even if a stale trigger row exists, so the broken state cleans itself up on save (agentTrigger=null) instead of trapping the user behind a validation error they can't dismiss.

Extends EventModal.test.tsx with three save-path tests: enabled-on-save forwards a complete agentTrigger object, toggle-off-on-existing-trigger forwards null, personal-event save omits agentTrigger entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
apps/web/src/components/calendar/MobileDayAgenda.tsx (1)

212-214: ⚡ Quick win

Prefer sr-only text over aria-label on the inline icon (lucide's own recommendation)

Passing aria-label to a lucide-react icon removes the default aria-hidden="true" and makes the icon visible to screen readers — so it does work. However, lucide's own accessibility guide explicitly recommends against using aria-label on icons, preferring the CSS framework's "visually hidden" utility instead, and links to further reading on why aria-label may not be ideal.

Since the icon is rendered inside a <h3> alongside the event title text, it's also worth noting that descriptions should only be provided to standalone icons that aren't purely decorative — providing accessible names to non-functional elements only increases clutter when using screen readers.

The recommended pattern for this case is to make the icon decorative and expose the supplemental information via a sr-only span:

♻️ Proposed refactor
-          {event.hasAgentTrigger && (
-            <Zap className="inline-block h-3.5 w-3.5 mr-1 text-amber-500 align-text-top" aria-label="Agent trigger configured" />
-          )}
-          {event.title}
+          {event.hasAgentTrigger && (
+            <>
+              <Zap className="inline-block h-3.5 w-3.5 mr-1 text-amber-500 align-text-top" aria-hidden="true" />
+              <span className="sr-only">Agent trigger configured. </span>
+            </>
+          )}
+          {event.title}

The same pattern applies to AgendaView.tsx (Line 191) and the other calendar view files updated in this PR.

🤖 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 `@apps/web/src/components/calendar/MobileDayAgenda.tsx` around lines 212 - 214,
Make the Zap icon decorative instead of giving it an aria-label: remove the
aria-label prop from the lucide <Zap> usage inside MobileDayAgenda (the
conditional using event.hasAgentTrigger) and add a visually-hidden span (e.g.,
className="sr-only") next to the icon containing the descriptive text like
"Agent trigger configured"; do the same refactor for the analogous icon usages
in AgendaView.tsx and other calendar view components so icons remain aria-hidden
while the supplemental text is exposed via the sr-only span.
🤖 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.

Nitpick comments:
In `@apps/web/src/components/calendar/MobileDayAgenda.tsx`:
- Around line 212-214: Make the Zap icon decorative instead of giving it an
aria-label: remove the aria-label prop from the lucide <Zap> usage inside
MobileDayAgenda (the conditional using event.hasAgentTrigger) and add a
visually-hidden span (e.g., className="sr-only") next to the icon containing the
descriptive text like "Agent trigger configured"; do the same refactor for the
analogous icon usages in AgendaView.tsx and other calendar view components so
icons remain aria-hidden while the supplemental text is exposed via the sr-only
span.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f00d991-06f7-48d5-a622-ace65938cd4b

📥 Commits

Reviewing files that changed from the base of the PR and between bf55a30 and 8f22a1a.

📒 Files selected for processing (12)
  • apps/web/src/components/agent-triggers/AgentTriggerSection.tsx
  • apps/web/src/components/calendar/AgendaView.tsx
  • apps/web/src/components/calendar/CalendarView.tsx
  • apps/web/src/components/calendar/DayView.tsx
  • apps/web/src/components/calendar/EventAgentTriggerDialog.tsx
  • apps/web/src/components/calendar/EventModal.tsx
  • apps/web/src/components/calendar/MobileDayAgenda.tsx
  • apps/web/src/components/calendar/MonthView.tsx
  • apps/web/src/components/calendar/WeekView.tsx
  • apps/web/src/components/calendar/__tests__/EventAgentTriggerDialog.test.tsx
  • apps/web/src/components/calendar/__tests__/EventModal.test.tsx
  • apps/web/src/components/calendar/useCalendarData.ts
💤 Files with no reviewable changes (2)
  • apps/web/src/components/calendar/EventAgentTriggerDialog.tsx
  • apps/web/src/components/calendar/tests/EventAgentTriggerDialog.test.tsx

…ggers

Aligns the agent-trigger Zap indicator on Month/Week/Day/Agenda/MobileDayAgenda chips to TaskListView's pattern (aria-hidden icon + sr-only text label) so screen readers announce it once with surrounding context, instead of independently via aria-label.

Tightens save semantics so a recurring event with a stale trigger row no longer silently deletes the row on every unrelated edit. The new guard (existingTrigger && !isRecurring) makes agentTrigger=null only fire when the user explicitly toggled off a configured trigger they could see, not as a side effect of editing the title.

Adds a test pinning that defensive behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai Already addressed in commit 4ac9d73 — the same aria-hidden + sr-only pattern is now applied in MobileDayAgenda.tsx, AgendaView.tsx, MonthView.tsx, WeekView.tsx, and DayView.tsx (both pill renderers). Matches TaskListView.tsx for cross-surface consistency. Thanks for the catch.

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@2witstudios: Great, thanks for the thorough follow-through! Applying the aria-hidden + sr-only pattern consistently across all five view files and aligning it with the TaskListView.tsx precedent is exactly the right approach for cross-surface accessibility consistency.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

…aries

- Replace single-member union `AgentTriggerSavePayload = | { ... }` with
  a plain object type; the leading `|` was syntactic noise.
- Replace nested ternaries in `lastRunStatusFor` and `agentHeaderLabel`
  with explicit if/else chains per project standard.
- Collapse the two duplicate-body `else if` branches in the trigger
  hydration effect into one combined predicate.
- Extract `firstSavePayload(onSave)` helper in the test file to remove
  the repeated `waitFor → cast → access` block from four tests.

No behavior changes. `pnpm --filter web typecheck` clean and all 10
EventModal tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@2witstudios
2witstudios merged commit 15d8d66 into master May 4, 2026
3 checks passed
@2witstudios
2witstudios deleted the pu/cal-parity branch May 5, 2026 02:56
2witstudios added a commit that referenced this pull request May 15, 2026
* refactor(calendar): de-emphasize agent UX in event modal

Replaces the always-visible 'Configure agent trigger' button (and its separate dialog) with an inline collapsed disclosure inside EventModal. Header reads 'Off' or '<agent name> at start' so the modal reads as a normal calendar by default and surfaces which agent is attached when one is, without a second-dialog hop.

Moves 'Linked page' under a collapsed Advanced section so casual users see only normal calendar fields. Adds a small Zap indicator on event chips in Month/Week/Day/Agenda/MobileDayAgenda whenever hasAgentTrigger is true, matching the task list's amber Trigger badge.

Wires agentTrigger (three-state: undefined no-op, null remove, object upsert) through useCalendarData and CalendarView.handleEventSave to the existing POST/PATCH endpoints. Recurring events render an inline 'can't have agent triggers' note instead of the form. Personal events get no agent affordance at all.

Retires EventAgentTriggerDialog + its test in favor of EventModal.test.tsx covering the inline disclosure, Advanced collapse, recurring guard, and SWR pause across remote refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(calendar): a11y + recurring-trigger defense in event modal

Adds a sr-only DialogDescription to EventModal so the Radix Dialog has an aria-describedby (fixes the missing-Description warning that was emitted on every open).

Wires the agent-trigger Switch to its Label via id/htmlFor so the toggle has an accessible name.

Defensively keeps agentEnabled=false for recurring events even if a stale trigger row exists, so the broken state cleans itself up on save (agentTrigger=null) instead of trapping the user behind a validation error they can't dismiss.

Extends EventModal.test.tsx with three save-path tests: enabled-on-save forwards a complete agentTrigger object, toggle-off-on-existing-trigger forwards null, personal-event save omits agentTrigger entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(calendar): align Zap a11y + don't auto-delete stale recurring triggers

Aligns the agent-trigger Zap indicator on Month/Week/Day/Agenda/MobileDayAgenda chips to TaskListView's pattern (aria-hidden icon + sr-only text label) so screen readers announce it once with surrounding context, instead of independently via aria-label.

Tightens save semantics so a recurring event with a stale trigger row no longer silently deletes the row on every unrelated edit. The new guard (existingTrigger && !isRecurring) makes agentTrigger=null only fire when the user explicitly toggled off a configured trigger they could see, not as a side effect of editing the title.

Adds a test pinning that defensive behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(calendar): tidy event modal — drop noise union, flatten ternaries

- Replace single-member union `AgentTriggerSavePayload = | { ... }` with
  a plain object type; the leading `|` was syntactic noise.
- Replace nested ternaries in `lastRunStatusFor` and `agentHeaderLabel`
  with explicit if/else chains per project standard.
- Collapse the two duplicate-body `else if` branches in the trigger
  hydration effect into one combined predicate.
- Extract `firstSavePayload(onSave)` helper in the test file to remove
  the repeated `waitFor → cast → access` block from four tests.

No behavior changes. `pnpm --filter web typecheck` clean and all 10
EventModal tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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