@agent-native/core 0.180.0-nightly-20260915184036 → 0.180.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  3. package/corpus/templates/calendar/CHANGELOG.md +39 -0
  4. package/corpus/templates/calendar/INTERACTION-MATRIX.md +19 -17
  5. package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +21 -0
  6. package/corpus/templates/calendar/actions/view-screen.ts +10 -2
  7. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +11 -0
  8. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +174 -145
  9. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +20 -16
  10. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +1 -0
  11. package/corpus/templates/calendar/app/hooks/use-events.ts +16 -0
  12. package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +126 -14
  13. package/corpus/templates/calendar/app/i18n-data.ts +90 -0
  14. package/corpus/templates/calendar/app/lib/calendar-navigation.ts +29 -9
  15. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +2 -0
  16. package/corpus/templates/calendar/app/lib/event-time-range.ts +34 -0
  17. package/corpus/templates/calendar/app/pages/CalendarView.tsx +245 -35
  18. package/corpus/templates/calendar/changelog/2026-09-13-calendar-sidebar-overflow-is-easier-to-discover.md +6 -0
  19. package/corpus/templates/calendar/changelog/2026-09-15-calendar-mirrors-notion-calendar-s-customizable-day-ranges-a.md +6 -0
  20. package/corpus/templates/calendar/shared/calendar-view-preferences.ts +36 -0
  21. package/corpus/templates/calendar/shared/timezone.ts +9 -2
  22. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +2 -0
  23. package/corpus/templates/clips/CHANGELOG.md +19 -0
  24. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +43 -4
  25. package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +114 -1
  26. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  27. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +108 -0
  28. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  29. package/corpus/templates/clips/changelog/2026-09-15-recordings-include-a-structured-timeline-of-browser-interact.md +6 -0
  30. package/corpus/templates/clips/chrome-extension/public/manifest.json +7 -0
  31. package/corpus/templates/clips/chrome-extension/src/background.ts +235 -9
  32. package/corpus/templates/clips/chrome-extension/src/content-history-bridge.ts +80 -0
  33. package/corpus/templates/clips/chrome-extension/src/content-script.ts +166 -5
  34. package/corpus/templates/clips/chrome-extension/vite.config.ts +4 -0
  35. package/corpus/templates/clips/server/db/schema.ts +3 -0
  36. package/corpus/templates/clips/server/lib/public-agent-context.ts +40 -2
  37. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  38. package/corpus/templates/clips/shared/browser-diagnostics.ts +171 -0
  39. package/corpus/templates/content/.agents/skills/content/SKILL.md +4 -2
  40. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -1
  41. package/corpus/templates/content/AGENTS.md +5 -6
  42. package/corpus/templates/content/CHANGELOG.md +24 -0
  43. package/corpus/templates/content/actions/_content-database-row-migration.ts +67 -36
  44. package/corpus/templates/content/actions/_document-edit-mutation.ts +74 -23
  45. package/corpus/templates/content/actions/edit-document.ts +48 -8
  46. package/corpus/templates/content/actions/migrate-content-database-rows.ts +69 -28
  47. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
  48. package/corpus/templates/design/CHANGELOG.md +22 -0
  49. package/corpus/templates/factory/CHANGELOG.md +11 -0
  50. package/corpus/templates/forms/CHANGELOG.md +6 -0
  51. package/corpus/templates/mail/.agents/skills/mail-interaction-parity/references/interaction-matrix.md +24 -4
  52. package/corpus/templates/mail/CHANGELOG.md +68 -0
  53. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +8 -0
  54. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +33 -13
  55. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +23 -4
  56. package/corpus/templates/mail/app/components/email/EmailThread.tsx +56 -13
  57. package/corpus/templates/mail/app/components/email/InlineReplyComposer.tsx +23 -4
  58. package/corpus/templates/mail/app/components/email/RecipientInput.tsx +9 -0
  59. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +8 -1
  60. package/corpus/templates/mail/app/i18n/en-US.ts +31 -0
  61. package/corpus/templates/mail/changelog/2026-09-15-mail-labels-recipient-and-account-controls-for-keyboard-and-.md +6 -0
  62. package/corpus/templates/slides/CHANGELOG.md +32 -0
  63. package/dist/client/CommandMenu.js +14 -2
  64. package/dist/client/settings/AccountSettingsCard.js +3 -2
  65. package/dist/client/settings/SettingsTabsPage.js +9 -1
  66. package/dist/client/settings/agent-settings-search.js +5 -1
  67. package/dist/client/sign-out.d.ts +1 -0
  68. package/dist/client/sign-out.js +8 -0
  69. package/dist/collab/routes.d.ts +1 -1
  70. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  71. package/dist/observability/routes.d.ts +3 -3
  72. package/dist/org/handlers.d.ts +1 -1
  73. package/dist/resources/handlers.d.ts +1 -1
  74. package/dist/secrets/routes.d.ts +3 -3
  75. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  76. package/dist/server/transcribe-voice.d.ts +1 -1
  77. package/package.json +4 -4
@@ -8,11 +8,12 @@ import {
8
8
  startOfWeek,
9
9
  endOfWeek,
10
10
  eachDayOfInterval,
11
+ getWeek,
11
12
  isSameMonth,
12
13
  isSameDay,
13
14
  format,
14
15
  } from "date-fns";
15
- import { memo, useState, useMemo, useRef } from "react";
16
+ import { Fragment, memo, useState, useMemo, useRef } from "react";
16
17
 
17
18
  import {
18
19
  Tooltip,
@@ -131,6 +132,7 @@ export const MonthView = memo(function MonthView({
131
132
  const headers = getWeekdayOrder(weekStartsOn)
132
133
  .filter((day) => !prefs.hideWeekends || (day !== 0 && day !== 6))
133
134
  .map((day) => headerLabels[day]);
135
+ const gridColumnCount = colCount + (prefs.showWeekNumbers ? 1 : 0);
134
136
 
135
137
  // Pre-group events by every day they overlap (not just their start day) so
136
138
  // multi-day events keep appearing as the grid moves past their start date.
@@ -203,8 +205,15 @@ export const MonthView = memo(function MonthView({
203
205
  {/* Weekday headers */}
204
206
  <div
205
207
  className="grid border-b border-border bg-card"
206
- style={{ gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }}
208
+ style={{
209
+ gridTemplateColumns: `repeat(${gridColumnCount}, minmax(0, 1fr))`,
210
+ }}
207
211
  >
212
+ {prefs.showWeekNumbers && (
213
+ <div className="py-2 text-center text-[10px] font-medium tracking-wide text-muted-foreground sm:py-2.5 sm:text-xs">
214
+ {t("calendarView.week")}
215
+ </div>
216
+ )}
208
217
  {headers.map((day, i) => (
209
218
  <div
210
219
  key={`${day}-${i}`}
@@ -218,9 +227,11 @@ export const MonthView = memo(function MonthView({
218
227
  {/* Day grid */}
219
228
  <div
220
229
  className="grid flex-1 auto-rows-fr"
221
- style={{ gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }}
230
+ style={{
231
+ gridTemplateColumns: `repeat(${gridColumnCount}, minmax(0, 1fr))`,
232
+ }}
222
233
  >
223
- {days.map((day) => {
234
+ {days.map((day, index) => {
224
235
  const dayOccurrences =
225
236
  eventsByDay.get(dateToCalendarDateKey(day)) ?? [];
226
237
  const inMonth = isSameMonth(day, selectedDate);
@@ -232,154 +243,172 @@ export const MonthView = memo(function MonthView({
232
243
  const isDragTarget = dragOverDay === dayKey;
233
244
 
234
245
  return (
235
- <div
236
- key={dayKey}
237
- data-calendar-create-surface="true"
238
- onClick={(e) => {
239
- if ((e.target as HTMLElement).closest("button")) return;
240
- if (shouldSuppressAfterPopoverClose()) return;
241
- onDateSelect(day);
242
- }}
243
- onDragOver={(e) => handleDragOver(e, dayKey)}
244
- onDragEnter={(e) => {
245
- e.preventDefault();
246
- setDragOverDay(dayKey);
247
- }}
248
- onDragLeave={(e) => {
249
- // Only clear if leaving to outside this cell
250
- if (!e.currentTarget.contains(e.relatedTarget as Node)) {
251
- setDragOverDay(null);
252
- }
253
- }}
254
- onDrop={(e) => handleDrop(e, day)}
255
- className={cn(
256
- "group relative min-h-[60px] cursor-pointer border-b border-r border-border p-1 transition-colors sm:min-h-[90px] sm:p-1.5",
257
- !inMonth && "opacity-35",
258
- isDragTarget
259
- ? "bg-primary/10 ring-2 ring-inset ring-primary/50"
260
- : "hover:bg-accent/40",
261
- )}
262
- >
263
- {/* Date number */}
264
- <div className="flex items-center justify-between">
265
- <span
266
- className={cn(
267
- "flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium sm:h-7 sm:w-7 sm:text-sm",
268
- today && "bg-primary text-primary-foreground font-semibold",
269
- selected && !today && "bg-accent text-accent-foreground",
270
- !today && !selected && "text-foreground",
271
- )}
246
+ <Fragment key={dayKey}>
247
+ {prefs.showWeekNumbers && index % colCount === 0 && (
248
+ <div
249
+ aria-label={t("calendarView.weekNumber", {
250
+ number: getWeek(day, {
251
+ weekStartsOn,
252
+ firstWeekContainsDate: 1,
253
+ }),
254
+ })}
255
+ className="border-b border-r border-border bg-muted/20 px-1 py-2 text-center text-[10px] font-medium text-muted-foreground sm:text-xs"
272
256
  >
273
- {format(day, "d")}
274
- </span>
275
-
276
- {inMonth && onCreateWorkingLocation && (
277
- <Tooltip>
278
- <TooltipTrigger asChild>
279
- <button
280
- type="button"
281
- aria-label={t("calendarView.addWorkingLocation")}
282
- className="mr-0.5 flex size-5 items-center justify-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"
283
- onClick={(event) => {
284
- event.stopPropagation();
285
- onCreateWorkingLocation(day);
286
- }}
287
- >
288
- <IconPlus aria-hidden="true" className="size-3.5" />
289
- </button>
290
- </TooltipTrigger>
291
- <TooltipContent side="top">
292
- {t("calendarView.addWorkingLocation")}
293
- </TooltipContent>
294
- </Tooltip>
257
+ {getWeek(day, {
258
+ weekStartsOn,
259
+ firstWeekContainsDate: 1,
260
+ })}
261
+ </div>
262
+ )}
263
+ <div
264
+ data-calendar-create-surface="true"
265
+ onClick={(e) => {
266
+ if ((e.target as HTMLElement).closest("button")) return;
267
+ if (shouldSuppressAfterPopoverClose()) return;
268
+ onDateSelect(day);
269
+ }}
270
+ onDragOver={(e) => handleDragOver(e, dayKey)}
271
+ onDragEnter={(e) => {
272
+ e.preventDefault();
273
+ setDragOverDay(dayKey);
274
+ }}
275
+ onDragLeave={(e) => {
276
+ // Only clear if leaving to outside this cell
277
+ if (!e.currentTarget.contains(e.relatedTarget as Node)) {
278
+ setDragOverDay(null);
279
+ }
280
+ }}
281
+ onDrop={(e) => handleDrop(e, day)}
282
+ className={cn(
283
+ "group relative min-h-[60px] cursor-pointer border-b border-r border-border p-1 transition-colors sm:min-h-[90px] sm:p-1.5",
284
+ !inMonth && "opacity-35",
285
+ isDragTarget
286
+ ? "bg-primary/10 ring-2 ring-inset ring-primary/50"
287
+ : "hover:bg-accent/40",
295
288
  )}
296
- </div>
289
+ >
290
+ {/* Date number */}
291
+ <div className="flex items-center justify-between">
292
+ <span
293
+ className={cn(
294
+ "flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium sm:h-7 sm:w-7 sm:text-sm",
295
+ today &&
296
+ "bg-primary text-primary-foreground font-semibold",
297
+ selected && !today && "bg-accent text-accent-foreground",
298
+ !today && !selected && "text-foreground",
299
+ )}
300
+ >
301
+ {format(day, "d")}
302
+ </span>
297
303
 
298
- {/* Events / Skeleton */}
299
- <div className="mt-1 space-y-0.5 overflow-hidden">
300
- {isLoading &&
301
- MONTH_SKELETON_WIDTHS[day.getDay()].map((w, i) => (
302
- <div
303
- key={i}
304
- className="h-4 animate-pulse rounded bg-muted"
305
- style={{ width: w }}
306
- />
307
- ))}
308
- {!isLoading &&
309
- dayOccurrences
310
- .slice(0, isMobile ? 2 : 3)
311
- .map(({ event, isStart, continuesNext }) => (
312
- <EventDetailPopover
313
- key={getCalendarEventRenderKey(event)}
314
- event={event}
315
- timezone={timezone}
316
- onDelete={onDeleteEvent ?? (() => {})}
317
- isDraft={draftEventIds.includes(event.id)}
318
- onDraftUpdate={onDraftUpdate}
319
- onDraftCreate={onDraftCreate}
320
- onDraftDiscard={onDraftDiscard}
321
- >
322
- <div
323
- onClick={(e) => e.stopPropagation()}
324
- onDragStart={(e) => {
325
- if (!isStart) return;
326
- const ghost = e.currentTarget.querySelector(
327
- "button",
328
- ) as HTMLElement | null;
329
- if (ghost) {
330
- e.dataTransfer.setDragImage(ghost, 12, 12);
331
- }
304
+ {inMonth && onCreateWorkingLocation && (
305
+ <Tooltip>
306
+ <TooltipTrigger asChild>
307
+ <button
308
+ type="button"
309
+ aria-label={t("calendarView.addWorkingLocation")}
310
+ className="mr-0.5 flex size-5 items-center justify-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"
311
+ onClick={(event) => {
312
+ event.stopPropagation();
313
+ onCreateWorkingLocation(day);
332
314
  }}
333
- className={cn(
334
- "relative",
335
- !isStart &&
336
- "-ml-1 -mr-1 border-l-2 border-dashed border-current pl-[calc(0.25rem-2px)] opacity-90 sm:-ml-1.5 sm:-mr-1.5 sm:pl-[calc(0.375rem-2px)]",
337
- )}
338
315
  >
339
- <EventCard
340
- event={event}
341
- colorPreferences={prefs}
342
- compact
343
- draggable={isStart}
344
- onDragStart={(draggedEvent) => {
345
- draggedEventRef.current = draggedEvent;
346
- setDraggingEvent(draggedEvent);
347
- }}
348
- onDragEnd={() => {
349
- draggedEventRef.current = null;
350
- setDraggingEvent(null);
351
- setDragOverDay(null);
352
- }}
353
- dimmed={
354
- draggingEvent !== null &&
355
- getCalendarEventRenderKey(draggingEvent) ===
356
- getCalendarEventRenderKey(event)
357
- }
358
- />
359
- {continuesNext && (
360
- <span
361
- aria-hidden="true"
362
- className="pointer-events-none absolute right-0.5 top-1/2 -translate-y-1/2 text-[9px] leading-none text-current opacity-60"
363
- >
364
- &rsaquo;
365
- </span>
366
- )}
367
- </div>
368
- </EventDetailPopover>
316
+ <IconPlus aria-hidden="true" className="size-3.5" />
317
+ </button>
318
+ </TooltipTrigger>
319
+ <TooltipContent side="top">
320
+ {t("calendarView.addWorkingLocation")}
321
+ </TooltipContent>
322
+ </Tooltip>
323
+ )}
324
+ </div>
325
+
326
+ {/* Events / Skeleton */}
327
+ <div className="mt-1 space-y-0.5 overflow-hidden">
328
+ {isLoading &&
329
+ MONTH_SKELETON_WIDTHS[day.getDay()].map((w, i) => (
330
+ <div
331
+ key={i}
332
+ className="h-4 animate-pulse rounded bg-muted"
333
+ style={{ width: w }}
334
+ />
369
335
  ))}
370
- {!isLoading && dayOccurrences.length > (isMobile ? 2 : 3) && (
371
- <button
372
- onClick={(e) => {
373
- e.stopPropagation();
374
- onDateSelect(day);
375
- }}
376
- className="block w-full rounded px-1 py-0.5 text-left text-[10px] text-muted-foreground hover:bg-accent/50 sm:px-1.5 sm:text-xs"
377
- >
378
- +{dayOccurrences.length - (isMobile ? 2 : 3)} more
379
- </button>
380
- )}
336
+ {!isLoading &&
337
+ dayOccurrences
338
+ .slice(0, isMobile ? 2 : 3)
339
+ .map(({ event, isStart, continuesNext }) => (
340
+ <EventDetailPopover
341
+ key={getCalendarEventRenderKey(event)}
342
+ event={event}
343
+ timezone={timezone}
344
+ onDelete={onDeleteEvent ?? (() => {})}
345
+ isDraft={draftEventIds.includes(event.id)}
346
+ onDraftUpdate={onDraftUpdate}
347
+ onDraftCreate={onDraftCreate}
348
+ onDraftDiscard={onDraftDiscard}
349
+ >
350
+ <div
351
+ onClick={(e) => e.stopPropagation()}
352
+ onDragStart={(e) => {
353
+ if (!isStart) return;
354
+ const ghost = e.currentTarget.querySelector(
355
+ "button",
356
+ ) as HTMLElement | null;
357
+ if (ghost) {
358
+ e.dataTransfer.setDragImage(ghost, 12, 12);
359
+ }
360
+ }}
361
+ className={cn(
362
+ "relative",
363
+ !isStart &&
364
+ "-ml-1 -mr-1 border-l-2 border-dashed border-current pl-[calc(0.25rem-2px)] opacity-90 sm:-ml-1.5 sm:-mr-1.5 sm:pl-[calc(0.375rem-2px)]",
365
+ )}
366
+ >
367
+ <EventCard
368
+ event={event}
369
+ colorPreferences={prefs}
370
+ compact
371
+ draggable={isStart}
372
+ onDragStart={(draggedEvent) => {
373
+ draggedEventRef.current = draggedEvent;
374
+ setDraggingEvent(draggedEvent);
375
+ }}
376
+ onDragEnd={() => {
377
+ draggedEventRef.current = null;
378
+ setDraggingEvent(null);
379
+ setDragOverDay(null);
380
+ }}
381
+ dimmed={
382
+ draggingEvent !== null &&
383
+ getCalendarEventRenderKey(draggingEvent) ===
384
+ getCalendarEventRenderKey(event)
385
+ }
386
+ />
387
+ {continuesNext && (
388
+ <span
389
+ aria-hidden="true"
390
+ className="pointer-events-none absolute right-0.5 top-1/2 -translate-y-1/2 text-[9px] leading-none text-current opacity-60"
391
+ >
392
+ &rsaquo;
393
+ </span>
394
+ )}
395
+ </div>
396
+ </EventDetailPopover>
397
+ ))}
398
+ {!isLoading && dayOccurrences.length > (isMobile ? 2 : 3) && (
399
+ <button
400
+ onClick={(e) => {
401
+ e.stopPropagation();
402
+ onDateSelect(day);
403
+ }}
404
+ className="block w-full rounded px-1 py-0.5 text-left text-[10px] text-muted-foreground hover:bg-accent/50 sm:px-1.5 sm:text-xs"
405
+ >
406
+ +{dayOccurrences.length - (isMobile ? 2 : 3)} more
407
+ </button>
408
+ )}
409
+ </div>
381
410
  </div>
382
- </div>
411
+ </Fragment>
383
412
  );
384
413
  })}
385
414
  </div>
@@ -10,12 +10,11 @@ import {
10
10
  } from "@tabler/icons-react";
11
11
  import {
12
12
  startOfWeek,
13
- endOfWeek,
14
- eachDayOfInterval,
15
13
  eachHourOfInterval,
16
14
  format,
17
15
  set,
18
16
  addMinutes,
17
+ addDays,
19
18
  } from "date-fns";
20
19
  import { useState, useEffect, useRef, useMemo, useCallback, memo } from "react";
21
20
 
@@ -41,6 +40,7 @@ import {
41
40
  partitionAllDayEvents,
42
41
  } from "@/lib/all-day-layout";
43
42
  import { getCalendarEventRenderKey } from "@/lib/calendar-event-identity";
43
+ import { getVisibleCalendarDays } from "@/lib/calendar-navigation";
44
44
  import {
45
45
  dateToCalendarDateKey,
46
46
  getBrowserTimezone,
@@ -49,6 +49,7 @@ import {
49
49
  getEventSegmentForCalendarDay,
50
50
  isAllDayCalendarEvent,
51
51
  } from "@/lib/calendar-timezone";
52
+ import { normalizeNumberOfDays } from "@/lib/calendar-view-preferences";
52
53
  import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
53
54
  import {
54
55
  computeTimedEventLayout,
@@ -113,6 +114,7 @@ interface WeekViewProps {
113
114
  onDraftDiscard?: (eventId: string) => void;
114
115
  isLoading?: boolean;
115
116
  weekStartsOn?: 0 | 1;
117
+ numberOfDays?: number;
116
118
  }
117
119
 
118
120
  // [startHour, startMin, durationMin, widthPct] per day column (Sun–Sat)
@@ -555,6 +557,7 @@ export const WeekView = memo(function WeekView({
555
557
  onDraftDiscard,
556
558
  isLoading = false,
557
559
  weekStartsOn = 0,
560
+ numberOfDays = 7,
558
561
  }: WeekViewProps) {
559
562
  const t = useT();
560
563
  const workingLocationLabels = useMemo(
@@ -602,30 +605,31 @@ export const WeekView = memo(function WeekView({
602
605
  }, []);
603
606
 
604
607
  const { prefs } = useViewPreferences();
605
- const weekStart = useMemo(
606
- () => startOfWeek(selectedDate, { weekStartsOn }),
607
- [selectedDate, weekStartsOn],
608
+ const displayedDayCount = normalizeNumberOfDays(numberOfDays);
609
+ const periodStart = useMemo(
610
+ () =>
611
+ displayedDayCount === 7
612
+ ? startOfWeek(selectedDate, { weekStartsOn })
613
+ : selectedDate,
614
+ [displayedDayCount, selectedDate, weekStartsOn],
608
615
  );
609
- const weekEnd = useMemo(
610
- () => endOfWeek(selectedDate, { weekStartsOn }),
611
- [selectedDate, weekStartsOn],
616
+ const periodEnd = useMemo(
617
+ () => addDays(periodStart, displayedDayCount - 1),
618
+ [displayedDayCount, periodStart],
612
619
  );
613
620
  // Stable day/hour arrays — recomputed only when the week or weekend
614
621
  // visibility actually changes, so memoized children (event buttons) don't
615
622
  // see a new array identity on every drag/focus re-render.
616
623
  const days = useMemo(() => {
617
- const fullWeek = eachDayOfInterval({ start: weekStart, end: weekEnd });
618
- return prefs.hideWeekends
619
- ? fullWeek.filter((d) => d.getDay() !== 0 && d.getDay() !== 6)
620
- : fullWeek;
621
- }, [weekStart, weekEnd, prefs.hideWeekends]);
624
+ return getVisibleCalendarDays(periodStart, periodEnd, prefs.hideWeekends);
625
+ }, [periodEnd, periodStart, prefs.hideWeekends]);
622
626
  const hours = useMemo(
623
627
  () =>
624
628
  eachHourOfInterval({
625
- start: set(weekStart, { hours: START_HOUR, minutes: 0 }),
626
- end: set(weekStart, { hours: END_HOUR - 1, minutes: 0 }),
629
+ start: set(periodStart, { hours: START_HOUR, minutes: 0 }),
630
+ end: set(periodStart, { hours: END_HOUR - 1, minutes: 0 }),
627
631
  }),
628
- [weekStart],
632
+ [periodStart],
629
633
  );
630
634
 
631
635
  // Separate all-day and timed events
@@ -769,6 +769,7 @@ export function Sidebar({
769
769
  collapsed={collapsed}
770
770
  collapsible={Boolean(onCollapsedChange)}
771
771
  onCollapsedChange={onCollapsedChange}
772
+ overflowAffordances
772
773
  brandName={t("navigation.brand")}
773
774
  brandHref="/home"
774
775
  secondaryItems={secondaryItems}
@@ -15,6 +15,7 @@ import { nanoid } from "nanoid";
15
15
  import { useMemo } from "react";
16
16
 
17
17
  import type { CalendarEventSourceIdentity } from "@/lib/calendar-event-identity";
18
+ import { isEventVisibleForDeclinedPreference } from "@/lib/calendar-view-preferences";
18
19
  import { dateTimeInTimezoneToIso } from "@/lib/event-form-utils";
19
20
  import {
20
21
  isSharedCalendarDemo,
@@ -625,6 +626,21 @@ export function findEventByCurrentOrReplacedId(
625
626
  return findCalendarEventForSelection(events, selectedEvent);
626
627
  }
627
628
 
629
+ export function findVisibleSelectedEvent(
630
+ events: CalendarEvent[],
631
+ selectedEvent: CalendarEvent,
632
+ showDeclinedEvents: boolean,
633
+ ): CalendarEvent | undefined {
634
+ const currentEvent =
635
+ findEventByCurrentOrReplacedId(events, selectedEvent) ?? selectedEvent;
636
+ return isEventVisibleForDeclinedPreference(
637
+ currentEvent.responseStatus,
638
+ showDeclinedEvents,
639
+ )
640
+ ? currentEvent
641
+ : undefined;
642
+ }
643
+
628
644
  export function useDeleteEvent() {
629
645
  const queryClient = useQueryClient();
630
646
  return useActionMutation<